You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This will cause the old bulkdata objects to be upgraded when loaded in the editor and saved out in the new format when the package is next saved. #rb PJ.Kack #rnx #preflight 609a4df10ba7b600015e0da2 * UE5CookerObjectVersion - Note that this work was done in CL 16266112 (it was easier to update this on the DevCooker branch) - Moved UE_USE_VIRTUALBULKDATA to VirtualizationManager. - Moved the virtualization versions to UE5MainStreamObjectVersion. - Note that no package would have been saved with UE_USE_VIRTUALBULKDATA so it is safe to just move the versions. * UE5MainStreamObjectVersion - Added the virtialization versions here - Fixed up some white space issues * VirtualizationManager - UE_USE_VIRTUALBULKDATA is now controlled from here. - When disabled (set to 0) a new code path UE_VBD_TO_OLD_BULKDATA_PATH will be enabled that will attempt to allow packages saved with VBD to be reverted safely. -- This requires that a new version be added to FUE5MainStreamObjectVersion, there is a static assert to warn people about this. * VirtualizedBulkData - Added a new method ::ConvertToOldBulkData which will load the VBD payload from disk and then pass it to the old bulkdata object provided. This is not as efficient as the OldBulkData->VBD path but is only provided as an emergancy way for us to back out if needed, we do not expect this code to actually be used. - Fixed a bug where converting an empty bulkdata object to VBD would give a warning about invalid guids. If the provided bulkdata object is empty then it is likely that we will not have generated a unique guid for it anyway. A VBD object with no payload would expect to have an unset FPayloadId AND an invalid bulkdata identifier anyway so we can just do that and only warn in the case that a valid payload has no valid guid provided. This has a bonus of making the code easier to read as well. * StaticMeshSourceData - Updated MESHDATAKEY_STATICMESH_DERIVEDDATA_VER when UE_USE_VIRTUALBULKDATA is enabled again. -- This probably isn't required but in theory some of our tests could've written mesh description data to the DDC with the old key, so we should update it to play it safe. * Texture - Converted to use FUE5MainStreamObjectVersion for it's version number - Added a code path that will allow us to convert back to using old style bulkdata if UE_USE_VIRTUALBULKDATA is disabled and UE_VBD_TO_OLD_BULKDATA_PATH is enabled. *MeshDescription - Converted to use FUE5MainStreamObjectVersion for it's version number -- Note that we don't have to tell the archive that we are using this because this was already happening. - Added a code path that will allow us to convert back to using old style bulkdata if UE_USE_VIRTUALBULKDATA is disabled and UE_VBD_TO_OLD_BULKDATA_PATH is enabled. [CL 16266408 by paul chipchase in ue5-main branch]
2236 lines
79 KiB
C++
2236 lines
79 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshDescription.h"
|
|
#include "MeshAttributes.h"
|
|
#include "Algo/Copy.h"
|
|
#include "Misc/SecureHash.h"
|
|
#include "Serialization/NameAsStringProxyArchive.h"
|
|
#include "Serialization/BulkDataReader.h"
|
|
#include "Serialization/BulkDataWriter.h"
|
|
#include "UObject/EnterpriseObjectVersion.h"
|
|
#include "UObject/UE5CookerObjectVersion.h"
|
|
#include "UObject/UE5MainStreamObjectVersion.h"
|
|
#include "Virtualization/VirtualizationUtilities.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Misc/ScopeRWLock.h"
|
|
#endif
|
|
|
|
FName FMeshDescription::VerticesName("Vertices");
|
|
FName FMeshDescription::VertexInstancesName("VertexInstances");
|
|
FName FMeshDescription::UVsName("UVs");
|
|
FName FMeshDescription::EdgesName("Edges");
|
|
FName FMeshDescription::TrianglesName("Triangles");
|
|
FName FMeshDescription::PolygonsName("Polygons");
|
|
FName FMeshDescription::PolygonGroupsName("PolygonGroups");
|
|
|
|
|
|
FMeshDescription::FMeshDescription()
|
|
{
|
|
Initialize();
|
|
}
|
|
|
|
|
|
FMeshDescription::FMeshDescription(const FMeshDescription& Other)
|
|
{
|
|
Elements = Other.Elements;
|
|
Cache();
|
|
}
|
|
|
|
|
|
FMeshDescription& FMeshDescription::operator=(const FMeshDescription& Other)
|
|
{
|
|
if (this != &Other)
|
|
{
|
|
Elements = Other.Elements;
|
|
Cache();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
void FMeshDescription::Initialize()
|
|
{
|
|
// Register the basic mesh element types
|
|
VertexElements = Elements.Emplace(VerticesName).Get();
|
|
VertexInstanceElements = Elements.Emplace(VertexInstancesName).Get();
|
|
UVElements = Elements.Emplace(UVsName).Get();
|
|
EdgeElements = Elements.Emplace(EdgesName).Get();
|
|
TriangleElements = Elements.Emplace(TrianglesName).Get();
|
|
PolygonElements = Elements.Emplace(PolygonsName).Get();
|
|
PolygonGroupElements = Elements.Emplace(PolygonGroupsName).Get();
|
|
|
|
// Now register topology-based attributes
|
|
|
|
// Register vertex reference for vertex instances
|
|
VertexInstanceVertices = VertexInstanceElements->Get().GetAttributes().RegisterIndexAttribute<FVertexID>(MeshAttribute::VertexInstance::VertexIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
|
|
// Register vertex references for edges
|
|
EdgeVertices = EdgeElements->Get().GetAttributes().RegisterIndexAttribute<FVertexID[2]>(MeshAttribute::Edge::VertexIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
|
|
// Register vertex instance and polygon references for triangles
|
|
TriangleVertexInstances = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FVertexInstanceID[3]>(MeshAttribute::Triangle::VertexInstanceIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
TrianglePolygons = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FPolygonID>(MeshAttribute::Triangle::PolygonIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
TrianglePolygonGroups = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FPolygonGroupID>(MeshAttribute::Triangle::PolygonGroupIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
|
|
// Register UV references for triangles
|
|
TriangleUVs = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FUVID[3]>(MeshAttribute::Triangle::UVIndex, 0, EMeshAttributeFlags::Mandatory);
|
|
|
|
// Register vertex and edge references for triangles; these are transient references which are generated at load time
|
|
TriangleVertices = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FVertexID[3]>(MeshAttribute::Triangle::VertexIndex, 1, EMeshAttributeFlags::Mandatory | EMeshAttributeFlags::Transient);
|
|
TriangleEdges = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FEdgeID[3]>(MeshAttribute::Triangle::EdgeIndex, 1, EMeshAttributeFlags::Mandatory | EMeshAttributeFlags::Transient);
|
|
|
|
// Register polygon group reference for polygons
|
|
PolygonPolygonGroups = PolygonElements->Get().GetAttributes().RegisterIndexAttribute<FPolygonGroupID>(MeshAttribute::Polygon::PolygonGroupIndex, 1, EMeshAttributeFlags::Mandatory);
|
|
|
|
// Minimal requirement is that vertices have a Position attribute
|
|
VertexPositions = VertexElements->Get().GetAttributes().RegisterAttribute(MeshAttribute::Vertex::Position, 1, FVector3f::ZeroVector, EMeshAttributeFlags::Lerpable | EMeshAttributeFlags::Mandatory);
|
|
|
|
// Register UVCoordinates attribute for UVs
|
|
UVElements->Get().GetAttributes().RegisterAttribute(MeshAttribute::UV::UVCoordinate, 1, FVector2D::ZeroVector, EMeshAttributeFlags::Lerpable | EMeshAttributeFlags::Mandatory);
|
|
|
|
// Associate indexers with element types and their referencing attributes
|
|
InitializeIndexers();
|
|
}
|
|
|
|
|
|
void FMeshDescription::InitializeIndexers()
|
|
{
|
|
// Vertices may typically have 6 vertex instances from the triangles which include them
|
|
VertexToVertexInstances.SetInitialNumReferences(6);
|
|
VertexToVertexInstances.Set(VertexElements, VertexInstanceElements, MeshAttribute::VertexInstance::VertexIndex);
|
|
|
|
// Vertices may typically have 6 edges from the triangles which include them
|
|
VertexToEdges.SetInitialNumReferences(6);
|
|
VertexToEdges.Set(VertexElements, EdgeElements, MeshAttribute::Edge::VertexIndex);
|
|
|
|
// @todo: VertexToTriangles?
|
|
VertexInstanceToTriangles.Set(VertexInstanceElements, TriangleElements, MeshAttribute::Triangle::VertexInstanceIndex);
|
|
|
|
// Assume most edges will have either 1 or 2 triangles
|
|
EdgeToTriangles.SetInitialNumReferences(2);
|
|
EdgeToTriangles.Set(EdgeElements, TriangleElements, MeshAttribute::Triangle::EdgeIndex);
|
|
|
|
// UVs may typically be used by 6 adjacent triangles
|
|
UVToTriangles.SetInitialNumReferences(6);
|
|
UVToTriangles.Set(UVElements, TriangleElements, MeshAttribute::Triangle::UVIndex);
|
|
|
|
// Assume most polygons are composed of only 1 triangle
|
|
PolygonToTriangles.SetInitialNumReferences(1);
|
|
PolygonToTriangles.Set(PolygonElements, TriangleElements, MeshAttribute::Triangle::PolygonIndex);
|
|
|
|
// Polygon group indexers are a little different; in general there will be many back references and few keys, so set not to use chunks.
|
|
PolygonGroupToTriangles.SetUnchunked();
|
|
PolygonGroupToPolygons.SetUnchunked();
|
|
PolygonGroupToTriangles.SetInitialNumReferences(256);
|
|
PolygonGroupToPolygons.SetInitialNumReferences(256);
|
|
PolygonGroupToTriangles.Set(PolygonGroupElements, TriangleElements, MeshAttribute::Triangle::PolygonGroupIndex);
|
|
PolygonGroupToPolygons.Set(PolygonGroupElements, PolygonElements, MeshAttribute::Polygon::PolygonGroupIndex);
|
|
}
|
|
|
|
|
|
void FMeshDescription::Cache()
|
|
{
|
|
// Get pointers to element containers
|
|
VertexElements = Elements.Find(VerticesName)->Get();
|
|
VertexInstanceElements = Elements.Find(VertexInstancesName)->Get();
|
|
UVElements = Elements.Find(UVsName)->Get();
|
|
EdgeElements = Elements.Find(EdgesName)->Get();
|
|
TriangleElements = Elements.Find(TrianglesName)->Get();
|
|
PolygonElements = Elements.Find(PolygonsName)->Get();
|
|
PolygonGroupElements = Elements.Find(PolygonGroupsName)->Get();
|
|
|
|
// Register required transient attributes
|
|
TriangleVertices = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FVertexID[3]>(MeshAttribute::Triangle::VertexIndex, 1, EMeshAttributeFlags::Mandatory | EMeshAttributeFlags::Transient);
|
|
TriangleEdges = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FEdgeID[3]>(MeshAttribute::Triangle::EdgeIndex, 1, EMeshAttributeFlags::Mandatory | EMeshAttributeFlags::Transient);
|
|
|
|
// Cache fundamental attribute arrays
|
|
VertexInstanceVertices = VertexInstanceElements->Get().GetAttributes().GetAttributesRef<FVertexID>(MeshAttribute::VertexInstance::VertexIndex);
|
|
EdgeVertices = EdgeElements->Get().GetAttributes().GetAttributesRef<TArrayView<FVertexID>>(MeshAttribute::Edge::VertexIndex);
|
|
TriangleVertexInstances = TriangleElements->Get().GetAttributes().GetAttributesRef<TArrayView<FVertexInstanceID>>(MeshAttribute::Triangle::VertexInstanceIndex);
|
|
TrianglePolygons = TriangleElements->Get().GetAttributes().GetAttributesRef<FPolygonID>(MeshAttribute::Triangle::PolygonIndex);
|
|
TrianglePolygonGroups = TriangleElements->Get().GetAttributes().GetAttributesRef<FPolygonGroupID>(MeshAttribute::Triangle::PolygonGroupIndex);
|
|
TriangleUVs = TriangleElements->Get().GetAttributes().GetAttributesRef<TArrayView<FUVID>>(MeshAttribute::Triangle::UVIndex);
|
|
PolygonPolygonGroups = PolygonElements->Get().GetAttributes().GetAttributesRef<FPolygonGroupID>(MeshAttribute::Polygon::PolygonGroupIndex);
|
|
VertexPositions = VertexElements->Get().GetAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
|
|
|
|
// Associate indexers with element types and their referencing attributes
|
|
InitializeIndexers();
|
|
}
|
|
|
|
|
|
void FMeshDescription::Serialize(FArchive& BaseAr)
|
|
{
|
|
FNameAsStringProxyArchive Ar(BaseAr);
|
|
|
|
Ar.UsingCustomVersion(FReleaseObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID);
|
|
|
|
if (Ar.IsLoading() && Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
UE_LOG(LogLoad, Warning, TEXT("Deprecated serialization format"));
|
|
}
|
|
|
|
if (Ar.IsLoading() &&
|
|
Ar.CustomVer(FReleaseObjectVersion::GUID) != FReleaseObjectVersion::MeshDescriptionNewFormat &&
|
|
Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::MeshDescriptionNewFormat)
|
|
{
|
|
// Serialize the old format data and transform it into the new format mesh element map
|
|
SerializeLegacy(Ar);
|
|
}
|
|
else
|
|
{
|
|
Ar << Elements;
|
|
|
|
// After loading elements, we need to re-cache mesh element and attribute arrays
|
|
if (Ar.IsLoading())
|
|
{
|
|
Cache();
|
|
|
|
for (FTriangleID TriangleID : TriangleElements->Get().GetElementIDs())
|
|
{
|
|
for (int32 I = 0; I < 3; I++)
|
|
{
|
|
FVertexInstanceID VertexInstanceID = TriangleVertexInstances[TriangleID][I];
|
|
TriangleVertices[TriangleID][I] = VertexInstanceVertices[VertexInstanceID];
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, TriangleID);
|
|
}
|
|
|
|
for (int32 I = 0; I < 3; I++)
|
|
{
|
|
FEdgeID EdgeID = GetVertexPairEdge(TriangleVertices[TriangleID][I], TriangleVertices[TriangleID][(I+1) % 3]);
|
|
TriangleEdges[TriangleID][I] = EdgeID;
|
|
EdgeToTriangles.AddReferenceToKey(EdgeID, TriangleID);
|
|
}
|
|
}
|
|
|
|
RebuildIndexers();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct FMeshVertex_Legacy
|
|
{
|
|
TArray<FVertexInstanceID> VertexInstanceIDs;
|
|
TArray<FEdgeID> ConnectedEdgeIDs;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshVertex_Legacy& Vertex)
|
|
{
|
|
check(Ar.IsLoading());
|
|
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
Ar << Vertex.VertexInstanceIDs;
|
|
Ar << Vertex.ConnectedEdgeIDs;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
struct FMeshVertexInstance_Legacy
|
|
{
|
|
FVertexID VertexID;
|
|
TArray<FTriangleID> ConnectedTriangles;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshVertexInstance_Legacy& VertexInstance)
|
|
{
|
|
check(Ar.IsLoading());
|
|
Ar << VertexInstance.VertexID;
|
|
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
TArray<FPolygonID> ConnectedPolygons_DISCARD;
|
|
Ar << ConnectedPolygons_DISCARD;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
struct FMeshEdge_Legacy
|
|
{
|
|
FVertexID VertexIDs[2];
|
|
TArray<FTriangleID> ConnectedTriangles;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshEdge_Legacy& Edge)
|
|
{
|
|
check(Ar.IsLoading());
|
|
Ar << Edge.VertexIDs[0];
|
|
Ar << Edge.VertexIDs[1];
|
|
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
TArray<FPolygonID> ConnectedPolygons_DISCARD;
|
|
Ar << ConnectedPolygons_DISCARD;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
struct FMeshTriangle_Legacy
|
|
{
|
|
FVertexInstanceID VertexInstanceIDs[3];
|
|
FPolygonID PolygonID;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshTriangle_Legacy& Triangle)
|
|
{
|
|
check(Ar.IsLoading());
|
|
Ar << Triangle.VertexInstanceIDs[0];
|
|
Ar << Triangle.VertexInstanceIDs[1];
|
|
Ar << Triangle.VertexInstanceIDs[2];
|
|
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::MeshDescriptionTriangles)
|
|
{
|
|
Ar << Triangle.PolygonID;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
struct FMeshPolygon_Legacy
|
|
{
|
|
TArray<FVertexInstanceID> VertexInstanceIDs;
|
|
TArray<FTriangleID> TriangleIDs;
|
|
FPolygonGroupID PolygonGroupID;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshPolygon_Legacy& Polygon)
|
|
{
|
|
check(Ar.IsLoading());
|
|
Ar << Polygon.VertexInstanceIDs;
|
|
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::MeshDescriptionRemovedHoles)
|
|
{
|
|
TArray<TArray<FVertexInstanceID>> Empty;
|
|
Ar << Empty;
|
|
}
|
|
|
|
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
TArray<FMeshTriangle_Legacy> Triangles_DISCARD;
|
|
Ar << Triangles_DISCARD;
|
|
}
|
|
|
|
Ar << Polygon.PolygonGroupID;
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
struct FMeshPolygonGroup_Legacy
|
|
{
|
|
TArray<FPolygonID> Polygons;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FMeshPolygonGroup_Legacy& PolygonGroup)
|
|
{
|
|
check(Ar.IsLoading());
|
|
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::MeshDescriptionNewSerialization)
|
|
{
|
|
Ar << PolygonGroup.Polygons;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
|
|
template <typename T>
|
|
struct FFixAttributesSizeHelper
|
|
{
|
|
explicit FFixAttributesSizeHelper(int32 InExpectedNum)
|
|
: ExpectedNum(InExpectedNum),
|
|
bAllDefault(true)
|
|
{}
|
|
|
|
template <typename U>
|
|
void operator()(const FName AttributeName, TMeshAttributesRef<T, TArrayView<U>> AttributeArrayRef)
|
|
{
|
|
// Not expecting arrays in legacy attributes
|
|
check(false);
|
|
}
|
|
|
|
template <typename U>
|
|
void operator()(const FName AttributeName, TMeshAttributesRef<T, TArrayAttribute<U>> AttributeArrayRef)
|
|
{
|
|
// Not expecting arrays in legacy attributes
|
|
check(false);
|
|
}
|
|
|
|
template <typename U>
|
|
void operator()(const FName AttributeName, TMeshAttributesRef<T, U> AttributeArrayRef)
|
|
{
|
|
if (bAllDefault)
|
|
{
|
|
for (int32 Channel = 0; Channel < AttributeArrayRef.GetNumChannels(); Channel++)
|
|
{
|
|
for (int32 Index = ExpectedNum; Index < AttributeArrayRef.GetNumElements(); Index++)
|
|
{
|
|
if (AttributeArrayRef.Get(T(Index), Channel) != AttributeArrayRef.GetDefaultValue())
|
|
{
|
|
bAllDefault = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 ExpectedNum;
|
|
bool bAllDefault;
|
|
};
|
|
|
|
|
|
template <typename T>
|
|
void FixAttributesSize(int32 ExpectedNum, TAttributesSet<T>& AttributesSet)
|
|
{
|
|
// Ensure that the attribute set is the same size as the mesh element array they describe
|
|
// If there are extra elements, and they are not set to trivial defaults, this is an error.
|
|
FFixAttributesSizeHelper<T> Helper(ExpectedNum);
|
|
AttributesSet.ForEach(Helper);
|
|
check(Helper.bAllDefault); // If this fires, something is very wrong with the legacy asset
|
|
AttributesSet.SetNumElements(ExpectedNum);
|
|
}
|
|
|
|
void FMeshDescription::SerializeLegacy(FArchive& Ar)
|
|
{
|
|
TMeshElementArray<FMeshVertex_Legacy, FVertexID> VertexArray;
|
|
TMeshElementArray<FMeshVertexInstance_Legacy, FVertexInstanceID> VertexInstanceArray;
|
|
TMeshElementArray<FMeshEdge_Legacy, FEdgeID> EdgeArray;
|
|
TMeshElementArray<FMeshTriangle_Legacy, FTriangleID> TriangleArray;
|
|
TMeshElementArray<FMeshPolygon_Legacy, FPolygonID> PolygonArray;
|
|
TMeshElementArray<FMeshPolygonGroup_Legacy, FPolygonGroupID> PolygonGroupArray;
|
|
|
|
TAttributesSet<FVertexID> VertexAttributesSet;
|
|
TAttributesSet<FVertexInstanceID> VertexInstanceAttributesSet;
|
|
TAttributesSet<FEdgeID> EdgeAttributesSet;
|
|
TAttributesSet<FTriangleID> TriangleAttributesSet;
|
|
TAttributesSet<FPolygonID> PolygonAttributesSet;
|
|
TAttributesSet<FPolygonGroupID> PolygonGroupAttributesSet;
|
|
|
|
Ar << VertexArray;
|
|
Ar << VertexInstanceArray;
|
|
Ar << EdgeArray;
|
|
Ar << PolygonArray;
|
|
Ar << PolygonGroupArray;
|
|
|
|
Ar << VertexAttributesSet;
|
|
Ar << VertexInstanceAttributesSet;
|
|
Ar << EdgeAttributesSet;
|
|
Ar << PolygonAttributesSet;
|
|
Ar << PolygonGroupAttributesSet;
|
|
|
|
FixAttributesSize(VertexArray.GetArraySize(), VertexAttributesSet);
|
|
FixAttributesSize(VertexInstanceArray.GetArraySize(), VertexInstanceAttributesSet);
|
|
FixAttributesSize(EdgeArray.GetArraySize(), EdgeAttributesSet);
|
|
FixAttributesSize(PolygonArray.GetArraySize(), PolygonAttributesSet);
|
|
FixAttributesSize(PolygonGroupArray.GetArraySize(), PolygonGroupAttributesSet);
|
|
|
|
// Serialize new triangle arrays since version MeshDescriptionTriangles
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::MeshDescriptionTriangles)
|
|
{
|
|
Ar << TriangleArray;
|
|
Ar << TriangleAttributesSet;
|
|
|
|
FixAttributesSize(TriangleArray.GetArraySize(), TriangleAttributesSet);
|
|
}
|
|
|
|
// Convert the old style element arrays into the new format
|
|
|
|
// Completely reinitialize the mesh elements map as it is not being directly serialized into.
|
|
Initialize();
|
|
|
|
for (FVertexID VertexID : VertexArray.GetElementIDs())
|
|
{
|
|
VertexElements->Get().Insert(VertexID);
|
|
}
|
|
|
|
for (FVertexInstanceID VertexInstanceID : VertexInstanceArray.GetElementIDs())
|
|
{
|
|
VertexInstanceElements->Get().Insert(VertexInstanceID);
|
|
FVertexID VertexID = VertexInstanceArray[VertexInstanceID].VertexID;
|
|
VertexInstanceVertices[VertexInstanceID] = VertexID;
|
|
VertexToVertexInstances.AddReferenceToKey(VertexID, VertexInstanceID);
|
|
}
|
|
|
|
for (FEdgeID EdgeID : EdgeArray.GetElementIDs())
|
|
{
|
|
EdgeElements->Get().Insert(EdgeID);
|
|
FVertexID VertexID0 = EdgeArray[EdgeID].VertexIDs[0];
|
|
FVertexID VertexID1 = EdgeArray[EdgeID].VertexIDs[1];
|
|
EdgeVertices[EdgeID][0] = VertexID0;
|
|
EdgeVertices[EdgeID][1] = VertexID1;
|
|
VertexToEdges.AddReferenceToKey(VertexID0, EdgeID);
|
|
VertexToEdges.AddReferenceToKey(VertexID1, EdgeID);
|
|
}
|
|
|
|
for (FPolygonGroupID PolygonGroupID : PolygonGroupArray.GetElementIDs())
|
|
{
|
|
PolygonGroupElements->Get().Insert(PolygonGroupID);
|
|
}
|
|
|
|
for (FPolygonID PolygonID : PolygonArray.GetElementIDs())
|
|
{
|
|
PolygonElements->Get().Insert(PolygonID);
|
|
FPolygonGroupID PolygonGroupID = PolygonArray[PolygonID].PolygonGroupID;
|
|
PolygonPolygonGroups[PolygonID] = PolygonGroupID;
|
|
PolygonGroupToPolygons.AddReferenceToKey(PolygonGroupID, PolygonID);
|
|
|
|
// If the asset is pre-triangles, we generate triangles here from the polygon
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::MeshDescriptionTriangles)
|
|
{
|
|
check(PolygonArray[PolygonID].VertexInstanceIDs.Num() >= 3);
|
|
CreatePolygonTriangles(PolygonID, PolygonArray[PolygonID].VertexInstanceIDs);
|
|
}
|
|
}
|
|
|
|
// Only do this if there were actually triangles in the asset
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::MeshDescriptionTriangles)
|
|
{
|
|
for (FTriangleID TriangleID : TriangleArray.GetElementIDs())
|
|
{
|
|
TriangleElements->Get().Insert(TriangleID);
|
|
FPolygonID PolygonID = TriangleArray[TriangleID].PolygonID;
|
|
FPolygonGroupID PolygonGroupID = PolygonArray[PolygonID].PolygonGroupID;
|
|
TrianglePolygons[TriangleID] = PolygonID;
|
|
TrianglePolygonGroups[TriangleID] = PolygonGroupID;
|
|
PolygonToTriangles.AddReferenceToKey(PolygonID, TriangleID);
|
|
PolygonGroupToTriangles.AddReferenceToKey(PolygonGroupID, TriangleID);
|
|
|
|
for (int32 I = 0; I < 3; I++)
|
|
{
|
|
FVertexInstanceID VertexInstanceID = TriangleArray[TriangleID].VertexInstanceIDs[I];
|
|
TriangleVertexInstances[TriangleID][I] = VertexInstanceID;
|
|
TriangleVertices[TriangleID][I] = VertexInstanceVertices[VertexInstanceID];
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, TriangleID);
|
|
}
|
|
|
|
for (int32 I = 0; I < 3; I++)
|
|
{
|
|
FEdgeID EdgeID = GetVertexPairEdge(TriangleVertices[TriangleID][I], TriangleVertices[TriangleID][(I+1) % 3]);
|
|
TriangleEdges[TriangleID][I] = EdgeID;
|
|
EdgeToTriangles.AddReferenceToKey(EdgeID, TriangleID);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unregister the position attribute from the vertex elements, as it will come from the legacy data instead.
|
|
VertexElements->Get().GetAttributes().UnregisterAttribute(MeshAttribute::Vertex::Position);
|
|
|
|
// Add the legacy mesh attributes into the new containers
|
|
VertexElements->Get().GetAttributes().AppendAttributesFrom(VertexAttributesSet);
|
|
VertexInstanceElements->Get().GetAttributes().AppendAttributesFrom(VertexInstanceAttributesSet);
|
|
EdgeElements->Get().GetAttributes().AppendAttributesFrom(EdgeAttributesSet);
|
|
PolygonElements->Get().GetAttributes().AppendAttributesFrom(PolygonAttributesSet);
|
|
PolygonGroupElements->Get().GetAttributes().AppendAttributesFrom(PolygonGroupAttributesSet);
|
|
|
|
if (Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::MeshDescriptionTriangles)
|
|
{
|
|
TriangleElements->Get().GetAttributes().AppendAttributesFrom(TriangleAttributesSet);
|
|
}
|
|
|
|
Cache();
|
|
BuildIndexers();
|
|
}
|
|
|
|
|
|
void FMeshDescription::ResetIndexers()
|
|
{
|
|
VertexToVertexInstances.Reset();
|
|
VertexToEdges.Reset();
|
|
VertexInstanceToTriangles.Reset();
|
|
EdgeToTriangles.Reset();
|
|
UVToTriangles.Reset();
|
|
PolygonToTriangles.Reset();
|
|
PolygonGroupToTriangles.Reset();
|
|
PolygonGroupToPolygons.Reset();
|
|
}
|
|
|
|
|
|
void FMeshDescription::BuildIndexers()
|
|
{
|
|
VertexToVertexInstances.Build();
|
|
VertexToEdges.Build();
|
|
VertexInstanceToTriangles.Build();
|
|
EdgeToTriangles.Build();
|
|
UVToTriangles.Build();
|
|
PolygonToTriangles.Build();
|
|
PolygonGroupToTriangles.Build();
|
|
PolygonGroupToPolygons.Build();
|
|
}
|
|
|
|
|
|
void FMeshDescription::RebuildIndexers()
|
|
{
|
|
VertexToVertexInstances.ForceRebuild();
|
|
VertexToEdges.ForceRebuild();
|
|
VertexInstanceToTriangles.ForceRebuild();
|
|
EdgeToTriangles.ForceRebuild();
|
|
UVToTriangles.ForceRebuild();
|
|
PolygonToTriangles.ForceRebuild();
|
|
PolygonGroupToTriangles.ForceRebuild();
|
|
PolygonGroupToPolygons.ForceRebuild();
|
|
}
|
|
|
|
|
|
void FMeshDescription::Empty()
|
|
{
|
|
VertexElements->Reset();
|
|
VertexInstanceElements->Reset();
|
|
UVElements->Reset();
|
|
EdgeElements->Reset();
|
|
TriangleElements->Reset();
|
|
PolygonElements->Reset();
|
|
PolygonGroupElements->Reset();
|
|
ResetIndexers();
|
|
}
|
|
|
|
|
|
bool FMeshDescription::IsEmpty() const
|
|
{
|
|
return VertexElements->IsEmpty() &&
|
|
VertexInstanceElements->IsEmpty() &&
|
|
UVElements->IsEmpty() &&
|
|
EdgeElements->IsEmpty() &&
|
|
TriangleElements->IsEmpty() &&
|
|
PolygonElements->IsEmpty() &&
|
|
PolygonGroupElements->IsEmpty();
|
|
}
|
|
|
|
|
|
void FMeshDescription::Compact(FElementIDRemappings& OutRemappings)
|
|
{
|
|
VertexElements->Get().Compact(OutRemappings.NewVertexIndexLookup);
|
|
VertexInstanceElements->Get().Compact(OutRemappings.NewVertexInstanceIndexLookup);
|
|
EdgeElements->Get().Compact(OutRemappings.NewEdgeIndexLookup);
|
|
TriangleElements->Get().Compact(OutRemappings.NewTriangleIndexLookup);
|
|
PolygonElements->Get().Compact(OutRemappings.NewPolygonIndexLookup);
|
|
PolygonGroupElements->Get().Compact(OutRemappings.NewPolygonGroupIndexLookup);
|
|
|
|
FixUpElementIDs(OutRemappings);
|
|
}
|
|
|
|
|
|
void FMeshDescription::Remap(const FElementIDRemappings& Remappings)
|
|
{
|
|
VertexElements->Get().Remap(Remappings.NewVertexIndexLookup);
|
|
VertexInstanceElements->Get().Remap(Remappings.NewVertexInstanceIndexLookup);
|
|
EdgeElements->Get().Remap(Remappings.NewEdgeIndexLookup);
|
|
TriangleElements->Get().Remap(Remappings.NewTriangleIndexLookup);
|
|
PolygonElements->Get().Remap(Remappings.NewPolygonIndexLookup);
|
|
PolygonGroupElements->Get().Remap(Remappings.NewPolygonGroupIndexLookup);
|
|
|
|
FixUpElementIDs(Remappings);
|
|
}
|
|
|
|
|
|
void FMeshDescription::FixUpElementIDs(const FElementIDRemappings& Remappings)
|
|
{
|
|
// Fix up vertex index references in vertex instance array
|
|
for (const FVertexInstanceID VertexInstanceID : VertexInstanceElements->Get().GetElementIDs())
|
|
{
|
|
VertexInstanceVertices[VertexInstanceID] = Remappings.GetRemappedVertexID(VertexInstanceVertices[VertexInstanceID]);
|
|
}
|
|
|
|
for (const FEdgeID EdgeID : EdgeElements->Get().GetElementIDs())
|
|
{
|
|
// Fix up vertex index references in Edges array
|
|
for (int32 Index = 0; Index < 2; Index++)
|
|
{
|
|
EdgeVertices[EdgeID][Index] = Remappings.GetRemappedVertexID(EdgeVertices[EdgeID][Index]);
|
|
}
|
|
}
|
|
|
|
for (const FTriangleID TriangleID : TriangleElements->Get().GetElementIDs())
|
|
{
|
|
// Fix up vertex instance references in Triangle
|
|
for (int32 Index = 0; Index < 3; Index++)
|
|
{
|
|
TriangleVertexInstances[TriangleID][Index] = Remappings.GetRemappedVertexInstanceID(TriangleVertexInstances[TriangleID][Index]);
|
|
TriangleEdges[TriangleID][Index] = Remappings.GetRemappedEdgeID(TriangleEdges[TriangleID][Index]);
|
|
TriangleVertices[TriangleID][Index] = Remappings.GetRemappedVertexID(TriangleVertices[TriangleID][Index]);
|
|
}
|
|
|
|
TrianglePolygons[TriangleID] = Remappings.GetRemappedPolygonID(TrianglePolygons[TriangleID]);
|
|
TrianglePolygonGroups[TriangleID] = Remappings.GetRemappedPolygonGroupID(TrianglePolygonGroups[TriangleID]);
|
|
}
|
|
|
|
for (const FPolygonID PolygonID : PolygonElements->Get().GetElementIDs())
|
|
{
|
|
PolygonPolygonGroups[PolygonID] = Remappings.GetRemappedPolygonGroupID(PolygonPolygonGroups[PolygonID]);
|
|
}
|
|
|
|
RebuildIndexers();
|
|
}
|
|
|
|
|
|
void FMeshDescription::CreateVertexInstance_Internal(const FVertexInstanceID VertexInstanceID, const FVertexID VertexID)
|
|
{
|
|
VertexInstanceVertices[VertexInstanceID] = VertexID;
|
|
VertexToVertexInstances.AddReferenceToKey(VertexID, VertexInstanceID);
|
|
}
|
|
|
|
|
|
template <template <typename...> class TContainer>
|
|
void FMeshDescription::DeleteVertexInstance_Internal(const FVertexInstanceID VertexInstanceID, TContainer<FVertexID>* InOutOrphanedVerticesPtr)
|
|
{
|
|
checkSlow(VertexInstanceToTriangles.Find(VertexInstanceID).Num() == 0);
|
|
|
|
const FVertexID VertexID = VertexInstanceVertices[VertexInstanceID];
|
|
VertexToVertexInstances.RemoveReferenceFromKey(VertexID, VertexInstanceID);
|
|
|
|
VertexInstanceElements->Get().Remove(VertexInstanceID);
|
|
VertexInstanceToTriangles.RemoveKey(VertexInstanceID);
|
|
|
|
// Always perform the Find() after the element has been completely deleted, so it won't be included in reindexing if the key is stale.
|
|
// Note: removing the element above will clear the attributes to default, so remember the VertexID we're interested in.
|
|
if (InOutOrphanedVerticesPtr &&
|
|
VertexToVertexInstances.Find(VertexID).Num() == 0 &&
|
|
VertexToEdges.Find(VertexID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedVerticesPtr, VertexID);
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::DeleteVertexInstance(const FVertexInstanceID VertexInstanceID, TArray<FVertexID>* InOutOrphanedVerticesPtr)
|
|
{
|
|
DeleteVertexInstance_Internal<TArray>(VertexInstanceID, InOutOrphanedVerticesPtr);
|
|
}
|
|
|
|
void FMeshDescription::CreateEdge_Internal(const FEdgeID EdgeID, const FVertexID VertexID0, const FVertexID VertexID1)
|
|
{
|
|
checkSlow(GetVertexPairEdge(VertexID0, VertexID1) == INDEX_NONE);
|
|
|
|
TArrayView<FVertexID> EdgeVertexIDs = EdgeVertices[EdgeID];
|
|
EdgeVertexIDs[0] = VertexID0;
|
|
EdgeVertexIDs[1] = VertexID1;
|
|
|
|
VertexToEdges.AddReferenceToKey(VertexID0, EdgeID);
|
|
VertexToEdges.AddReferenceToKey(VertexID1, EdgeID);
|
|
}
|
|
|
|
template <template <typename...> class TContainer>
|
|
void FMeshDescription::DeleteEdge_Internal(const FEdgeID EdgeID, TContainer<FVertexID>* InOutOrphanedVerticesPtr)
|
|
{
|
|
const FVertexID VertexID0 = EdgeVertices[EdgeID][0];
|
|
const FVertexID VertexID1 = EdgeVertices[EdgeID][1];
|
|
|
|
VertexToEdges.RemoveReferenceFromKey(VertexID0, EdgeID);
|
|
VertexToEdges.RemoveReferenceFromKey(VertexID1, EdgeID);
|
|
|
|
EdgeElements->Get().Remove(EdgeID);
|
|
EdgeToTriangles.RemoveKey(EdgeID);
|
|
|
|
// Always perform the Find() after the element has been completely deleted, so it won't be included in reindexing if the key is stale.
|
|
// Note: removing the element above will clear the attributes to default, so remember the VertexID we're interested in.
|
|
if (InOutOrphanedVerticesPtr)
|
|
{
|
|
if (VertexToEdges.Find(VertexID0).Num() == 0)
|
|
{
|
|
check(VertexToVertexInstances.Find(VertexID0).Num() == 0);
|
|
AddUnique(*InOutOrphanedVerticesPtr, VertexID0);
|
|
}
|
|
|
|
if (VertexToEdges.Find(VertexID1).Num() == 0)
|
|
{
|
|
check(VertexToVertexInstances.Find(VertexID1).Num() == 0);
|
|
AddUnique(*InOutOrphanedVerticesPtr, VertexID1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::DeleteEdge(const FEdgeID EdgeID, TArray<FVertexID>* InOutOrphanedVerticesPtr)
|
|
{
|
|
DeleteEdge_Internal<TArray>(EdgeID, InOutOrphanedVerticesPtr);
|
|
}
|
|
|
|
void FMeshDescription::CreateTriangle_Internal(const FTriangleID TriangleID, const FPolygonGroupID PolygonGroupID, TArrayView<const FVertexInstanceID> VertexInstanceIDs, TArray<FEdgeID>* OutEdgeIDs)
|
|
{
|
|
if (OutEdgeIDs)
|
|
{
|
|
OutEdgeIDs->Empty();
|
|
}
|
|
|
|
// Fill out triangle vertex instances
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
check(VertexInstanceIDs.Num() == 3);
|
|
TriVertexInstanceIDs[0] = VertexInstanceIDs[0];
|
|
TriVertexInstanceIDs[1] = VertexInstanceIDs[1];
|
|
TriVertexInstanceIDs[2] = VertexInstanceIDs[2];
|
|
|
|
// Fill out triangle polygon group
|
|
TrianglePolygonGroups[TriangleID] = PolygonGroupID;
|
|
PolygonGroupToTriangles.AddReferenceToKey(PolygonGroupID, TriangleID);
|
|
|
|
// Make a polygon which will contain this triangle
|
|
// @todo: make this optional; currently only exists for backwards compatibility
|
|
FPolygonID PolygonID = PolygonElements->Get().Add();
|
|
PolygonPolygonGroups[PolygonID] = PolygonGroupID;
|
|
PolygonGroupToPolygons.AddReferenceToKey(PolygonGroupID, PolygonID);
|
|
|
|
TrianglePolygons[TriangleID] = PolygonID;
|
|
PolygonToTriangles.AddReferenceToKey(PolygonID, TriangleID);
|
|
|
|
TArrayView<FVertexID> TriVertexIDs = TriangleVertices[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
const FVertexInstanceID VertexInstanceID = TriVertexInstanceIDs[Index];
|
|
const FVertexInstanceID NextVertexInstanceID = TriVertexInstanceIDs[(Index == 2) ? 0 : Index + 1];
|
|
|
|
const FVertexID ThisVertexID = VertexInstanceVertices[VertexInstanceID];
|
|
const FVertexID NextVertexID = VertexInstanceVertices[NextVertexInstanceID];
|
|
|
|
TriVertexIDs[Index] = ThisVertexID;
|
|
// VertexToTriangles.AddReferenceToKey(ThisVertexID, TriangleID); // @todo: can add this, but need to consider how degenerates would work
|
|
|
|
FEdgeID EdgeID = GetVertexPairEdge(ThisVertexID, NextVertexID);
|
|
if (EdgeID == INDEX_NONE)
|
|
{
|
|
EdgeID = CreateEdge(ThisVertexID, NextVertexID);
|
|
if (OutEdgeIDs)
|
|
{
|
|
OutEdgeIDs->Add(EdgeID);
|
|
}
|
|
}
|
|
|
|
TriEdgeIDs[Index] = EdgeID;
|
|
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, TriangleID);
|
|
EdgeToTriangles.AddReferenceToKey(EdgeID, TriangleID);
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::SetTriangleUVIndices(const FTriangleID TriangleID, TArrayView<const FUVID> UVIDs, int32 UVChannel)
|
|
{
|
|
TArrayView<FUVID> TriUVs = TriangleUVs.Get(TriangleID, UVChannel);
|
|
TriUVs[0] = UVIDs[0];
|
|
TriUVs[1] = UVIDs[1];
|
|
TriUVs[2] = UVIDs[2];
|
|
UVToTriangles.AddReferenceToKey(UVIDs[0], TriangleID, UVChannel);
|
|
UVToTriangles.AddReferenceToKey(UVIDs[1], TriangleID, UVChannel);
|
|
UVToTriangles.AddReferenceToKey(UVIDs[2], TriangleID, UVChannel);
|
|
}
|
|
|
|
|
|
template <template <typename...> class TContainer>
|
|
void FMeshDescription::DeleteTriangle_Internal(const FTriangleID TriangleID, TContainer<FEdgeID>* InOutOrphanedEdgesPtr, TContainer<FVertexInstanceID>* InOutOrphanedVertexInstancesPtr, TContainer<FPolygonGroupID>* InOutOrphanedPolygonGroupsPtr)
|
|
{
|
|
const FPolygonGroupID PolygonGroupID = TrianglePolygonGroups[TriangleID];
|
|
PolygonGroupToTriangles.RemoveReferenceFromKey(PolygonGroupID, TriangleID);
|
|
|
|
const FPolygonID PolygonID = TrianglePolygons[TriangleID];
|
|
PolygonToTriangles.RemoveReferenceFromKey(PolygonID, TriangleID);
|
|
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
// Remove vertex instance from the triangle. Here's how we do that safely:
|
|
// 1) Take a copy of the VertexInstance ID
|
|
// 2) Remove reference from the VertexInstance to Triangles indexer
|
|
// 3) Set the triangle VertexInstance to INDEX_NONE
|
|
// 4) Ask the indexer if that VertexInstance ID has any triangle references, and if not, add it to the list.
|
|
//
|
|
// Step 3 is important because Find() below might need to reindex if the key is stale, and this would cause it to
|
|
// re-add the triangle VertexInstance reference if it were still valid, since the triangle hasn't yet been deleted.
|
|
//
|
|
// We could also wait until the triangle were deleted before querying the indexer, but this would involve taking
|
|
// copies of all the referenced element IDs first.
|
|
|
|
const FVertexInstanceID VertexInstanceID = TriVertexInstanceIDs[Index];
|
|
|
|
VertexInstanceToTriangles.RemoveReferenceFromKey(VertexInstanceID, TriangleID);
|
|
TriVertexInstanceIDs[Index] = INDEX_NONE;
|
|
|
|
if (InOutOrphanedVertexInstancesPtr && VertexInstanceToTriangles.Find(VertexInstanceID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedVertexInstancesPtr, VertexInstanceID);
|
|
}
|
|
|
|
// Remove edge from the triangle. Same rules as above apply!
|
|
|
|
const FEdgeID EdgeID = TriEdgeIDs[Index];
|
|
|
|
EdgeToTriangles.RemoveReferenceFromKey(EdgeID, TriangleID);
|
|
TriEdgeIDs[Index] = INDEX_NONE;
|
|
|
|
if (InOutOrphanedEdgesPtr && EdgeToTriangles.Find(EdgeID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedEdgesPtr, EdgeID);
|
|
}
|
|
|
|
// Remove UVs from the triangle if there are any
|
|
|
|
for (int32 UVIndex = 0; UVIndex < TriangleUVs.GetNumChannels(); UVIndex++)
|
|
{
|
|
TArrayView<const FUVID> TriUVs = TriangleUVs.Get(TriangleID, UVIndex);
|
|
FUVID UVID = TriUVs[Index];
|
|
UVToTriangles.RemoveReferenceFromKey(UVID, TriangleID, UVIndex);
|
|
}
|
|
}
|
|
|
|
if (PolygonToTriangles.Find(PolygonID).Num() == 0)
|
|
{
|
|
// If it was the only triangle in the polygon, delete the polygon too
|
|
check(PolygonPolygonGroups[PolygonID] == PolygonGroupID);
|
|
PolygonGroupToPolygons.RemoveReferenceFromKey(PolygonGroupID, PolygonID);
|
|
|
|
PolygonElements->Get().Remove(PolygonID);
|
|
PolygonToTriangles.RemoveKey(PolygonID);
|
|
}
|
|
|
|
TriangleElements->Get().Remove(TriangleID);
|
|
|
|
// Check orphaned polygon groups after the triangle has been removed so that reindexing won't find it again.
|
|
if (InOutOrphanedPolygonGroupsPtr && PolygonGroupToTriangles.Find(PolygonGroupID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedPolygonGroupsPtr, PolygonGroupID);
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::DeleteTriangle(const FTriangleID TriangleID, TArray<FEdgeID>* InOutOrphanedEdgesPtr, TArray<FVertexInstanceID>* InOutOrphanedVertexInstancesPtr, TArray<FPolygonGroupID>* InOutOrphanedPolygonGroupsPtr)
|
|
{
|
|
DeleteTriangle_Internal<TArray>(TriangleID, InOutOrphanedEdgesPtr, InOutOrphanedVertexInstancesPtr, InOutOrphanedPolygonGroupsPtr);
|
|
}
|
|
|
|
void FMeshDescription::DeleteTriangles(const TArray<FTriangleID>& Triangles)
|
|
{
|
|
TSet<FEdgeID> OrphanedEdges;
|
|
TSet<FVertexInstanceID> OrphanedVertexInstances;
|
|
TSet<FPolygonGroupID> OrphanedPolygonGroups;
|
|
TSet<FVertexID> OrphanedVertices;
|
|
|
|
for (FTriangleID TriangleID : Triangles)
|
|
{
|
|
DeleteTriangle_Internal<TSet>(TriangleID, &OrphanedEdges, &OrphanedVertexInstances, &OrphanedPolygonGroups);
|
|
}
|
|
for (FPolygonGroupID PolygonGroupID : OrphanedPolygonGroups)
|
|
{
|
|
DeletePolygonGroup(PolygonGroupID);
|
|
}
|
|
for (FVertexInstanceID VertexInstanceID : OrphanedVertexInstances)
|
|
{
|
|
DeleteVertexInstance_Internal<TSet>(VertexInstanceID, &OrphanedVertices);
|
|
}
|
|
for (FEdgeID EdgeID : OrphanedEdges)
|
|
{
|
|
DeleteEdge_Internal<TSet>(EdgeID, &OrphanedVertices);
|
|
}
|
|
for (FVertexID VertexID : OrphanedVertices)
|
|
{
|
|
DeleteVertex(VertexID);
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::CreatePolygon_Internal(const FPolygonID PolygonID, const FPolygonGroupID PolygonGroupID, TArrayView<const FVertexInstanceID> VertexInstanceIDs, TArray<FEdgeID>* OutEdgeIDs)
|
|
{
|
|
if (OutEdgeIDs)
|
|
{
|
|
OutEdgeIDs->Empty();
|
|
}
|
|
|
|
check(PolygonGroupID != INDEX_NONE);
|
|
PolygonPolygonGroups[PolygonID] = PolygonGroupID;
|
|
PolygonGroupToPolygons.AddReferenceToKey(PolygonGroupID, PolygonID);
|
|
|
|
const int32 NumVertices = VertexInstanceIDs.Num();
|
|
for (int32 Index = 0; Index < NumVertices; ++Index)
|
|
{
|
|
const FVertexInstanceID ThisVertexInstanceID = VertexInstanceIDs[Index];
|
|
const FVertexInstanceID NextVertexInstanceID = VertexInstanceIDs[(Index + 1 == NumVertices) ? 0 : Index + 1];
|
|
const FVertexID ThisVertexID = VertexInstanceVertices[ThisVertexInstanceID];
|
|
const FVertexID NextVertexID = VertexInstanceVertices[NextVertexInstanceID];
|
|
|
|
// Create any missing perimeter edges here (not internal edges; they will be created when the polygon triangles are created)
|
|
FEdgeID EdgeID = GetVertexPairEdge(ThisVertexID, NextVertexID);
|
|
if (EdgeID == INDEX_NONE)
|
|
{
|
|
EdgeID = CreateEdge(ThisVertexID, NextVertexID);
|
|
if (OutEdgeIDs)
|
|
{
|
|
OutEdgeIDs->Add(EdgeID);
|
|
}
|
|
}
|
|
}
|
|
|
|
CreatePolygonTriangles(PolygonID, VertexInstanceIDs);
|
|
}
|
|
|
|
template <template <typename...> class TContainer>
|
|
void FMeshDescription::DeletePolygon_Internal(const FPolygonID PolygonID, TContainer<FEdgeID>* InOutOrphanedEdgesPtr, TContainer<FVertexInstanceID>* InOutOrphanedVertexInstancesPtr, TContainer<FPolygonGroupID>* InOutOrphanedPolygonGroupsPtr)
|
|
{
|
|
const FPolygonGroupID PolygonGroupID = PolygonPolygonGroups[PolygonID];
|
|
|
|
// Remove constituent triangles
|
|
TArrayView<const FTriangleID> TriangleIDs = PolygonToTriangles.Find<FTriangleID>(PolygonID);
|
|
|
|
TArray<FEdgeID, TInlineAllocator<8>> InternalEdgesToRemove;
|
|
InternalEdgesToRemove.Reserve(TriangleIDs.Num() - 1);
|
|
|
|
// Disconnect edges and vertex instances
|
|
for (const FTriangleID TriangleID : TriangleIDs)
|
|
{
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
const FEdgeID EdgeID = TriEdgeIDs[Index];
|
|
if (IsEdgeInternal(EdgeID))
|
|
{
|
|
// Remove internal edges - but not yet.
|
|
// For now just make a note of them.
|
|
InternalEdgesToRemove.AddUnique(EdgeID);
|
|
}
|
|
else
|
|
{
|
|
EdgeToTriangles.RemoveReferenceFromKey(EdgeID, TriangleID);
|
|
|
|
// Set the reference to INDEX_NONE here, so that it won't be picked up in the Find() below
|
|
// if it needs to rebuild the key.
|
|
TriEdgeIDs[Index] = INDEX_NONE;
|
|
|
|
if (InOutOrphanedEdgesPtr && EdgeToTriangles.Find(EdgeID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedEdgesPtr, EdgeID);
|
|
}
|
|
}
|
|
|
|
const FVertexInstanceID VertexInstanceID = TriVertexInstanceIDs[Index];
|
|
VertexInstanceToTriangles.RemoveReferenceFromKey(VertexInstanceID, TriangleID);
|
|
|
|
// Set the reference to INDEX_NONE here, so that it won't be picked up in the Find() below
|
|
// if it needs to rebuild the key.
|
|
TriVertexInstanceIDs[Index] = INDEX_NONE;
|
|
|
|
if (InOutOrphanedVertexInstancesPtr && VertexInstanceToTriangles.Find(VertexInstanceID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedVertexInstancesPtr, VertexInstanceID);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove triangles
|
|
for (const FTriangleID TriangleID : TriangleIDs)
|
|
{
|
|
check(TrianglePolygonGroups[TriangleID] == PolygonGroupID);
|
|
PolygonGroupToTriangles.RemoveReferenceFromKey(PolygonGroupID, TriangleID);
|
|
TriangleElements->Get().Remove(TriangleID);
|
|
}
|
|
|
|
// Remove internal edges
|
|
for (const FEdgeID EdgeID : InternalEdgesToRemove)
|
|
{
|
|
for (const FVertexID EdgeVertexID : EdgeVertices[EdgeID])
|
|
{
|
|
VertexToEdges.RemoveReferenceFromKey(EdgeVertexID, EdgeID);
|
|
}
|
|
|
|
EdgeElements->Get().Remove(EdgeID);
|
|
EdgeToTriangles.RemoveKey(EdgeID);
|
|
}
|
|
|
|
PolygonGroupToPolygons.RemoveReferenceFromKey(PolygonGroupID, PolygonID);
|
|
|
|
PolygonElements->Get().Remove(PolygonID);
|
|
PolygonToTriangles.RemoveKey(PolygonID);
|
|
|
|
if (InOutOrphanedPolygonGroupsPtr && PolygonGroupToPolygons.Find(PolygonGroupID).Num() == 0)
|
|
{
|
|
AddUnique(*InOutOrphanedPolygonGroupsPtr, PolygonGroupID);
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::DeletePolygon(const FPolygonID PolygonID, TArray<FEdgeID>* InOutOrphanedEdgesPtr, TArray<FVertexInstanceID>* InOutOrphanedVertexInstancesPtr, TArray<FPolygonGroupID>* InOutOrphanedPolygonGroupsPtr)
|
|
{
|
|
DeletePolygon_Internal<TArray>(PolygonID, InOutOrphanedEdgesPtr, InOutOrphanedVertexInstancesPtr, InOutOrphanedPolygonGroupsPtr);
|
|
}
|
|
|
|
void FMeshDescription::DeletePolygons(const TArray<FPolygonID>& Polygons)
|
|
{
|
|
TSet<FEdgeID> OrphanedEdges;
|
|
TSet<FVertexInstanceID> OrphanedVertexInstances;
|
|
TSet<FPolygonGroupID> OrphanedPolygonGroups;
|
|
TSet<FVertexID> OrphanedVertices;
|
|
|
|
for (FPolygonID PolygonID : Polygons)
|
|
{
|
|
DeletePolygon_Internal<TSet>(PolygonID, &OrphanedEdges, &OrphanedVertexInstances, &OrphanedPolygonGroups);
|
|
}
|
|
for (FPolygonGroupID PolygonGroupID : OrphanedPolygonGroups)
|
|
{
|
|
DeletePolygonGroup(PolygonGroupID);
|
|
}
|
|
for (FVertexInstanceID VertexInstanceID : OrphanedVertexInstances)
|
|
{
|
|
DeleteVertexInstance_Internal<TSet>(VertexInstanceID, &OrphanedVertices);
|
|
}
|
|
for (FEdgeID EdgeID : OrphanedEdges)
|
|
{
|
|
DeleteEdge_Internal<TSet>(EdgeID, &OrphanedVertices);
|
|
}
|
|
for (FVertexID VertexID : OrphanedVertices)
|
|
{
|
|
DeleteVertex(VertexID);
|
|
}
|
|
}
|
|
|
|
bool FMeshDescription::IsVertexOrphaned(const FVertexID VertexID) const
|
|
{
|
|
TArrayView<const FVertexInstanceID> VertexInstanceIDs = VertexToVertexInstances.Find<FVertexInstanceID>(VertexID);
|
|
for (const FVertexInstanceID VertexInstanceID : VertexInstanceIDs)
|
|
{
|
|
TArrayView<const FTriangleID> TriangleIDs = VertexInstanceToTriangles.Find<FTriangleID>(VertexInstanceID);
|
|
if (TriangleIDs.Num() > 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
FEdgeID FMeshDescription::GetVertexPairEdge(const FVertexID VertexID0, const FVertexID VertexID1) const
|
|
{
|
|
TArrayView<const FEdgeID> ConnectedEdgeIDs = VertexToEdges.Find<FEdgeID>(VertexID0);
|
|
for (const FEdgeID VertexConnectedEdgeID : ConnectedEdgeIDs)
|
|
{
|
|
const FVertexID EdgeVertexID0 = EdgeVertices[VertexConnectedEdgeID][0];
|
|
const FVertexID EdgeVertexID1 = EdgeVertices[VertexConnectedEdgeID][1];
|
|
if ((EdgeVertexID0 == VertexID0 && EdgeVertexID1 == VertexID1) || (EdgeVertexID0 == VertexID1 && EdgeVertexID1 == VertexID0))
|
|
{
|
|
return VertexConnectedEdgeID;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
|
|
FEdgeID FMeshDescription::GetVertexInstancePairEdge(const FVertexInstanceID VertexInstanceID0, const FVertexInstanceID VertexInstanceID1) const
|
|
{
|
|
const FVertexID VertexID0 = VertexInstanceVertices[VertexInstanceID0];
|
|
const FVertexID VertexID1 = VertexInstanceVertices[VertexInstanceID1];
|
|
TArrayView<const FEdgeID> ConnectedEdgeIDs = VertexToEdges.Find<FEdgeID>(VertexID0);
|
|
for (const FEdgeID VertexConnectedEdgeID : ConnectedEdgeIDs)
|
|
{
|
|
const FVertexID EdgeVertexID0 = EdgeVertices[VertexConnectedEdgeID][0];
|
|
const FVertexID EdgeVertexID1 = EdgeVertices[VertexConnectedEdgeID][1];
|
|
if ((EdgeVertexID0 == VertexID0 && EdgeVertexID1 == VertexID1) || (EdgeVertexID0 == VertexID1 && EdgeVertexID1 == VertexID0))
|
|
{
|
|
return VertexConnectedEdgeID;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
|
|
void FMeshDescription::SetPolygonVertexInstance(const FPolygonID PolygonID, const int32 PerimeterIndex, const FVertexInstanceID VertexInstanceID)
|
|
{
|
|
TArrayView<const FTriangleID> Tris = PolygonToTriangles.Find<FTriangleID>(PolygonID);
|
|
if (Tris.Num() == 1)
|
|
{
|
|
check(PerimeterIndex < 3);
|
|
FVertexInstanceID OldVertexInstanceID = TriangleVertexInstances[Tris[0]][PerimeterIndex];
|
|
check(VertexInstanceVertices[OldVertexInstanceID] == VertexInstanceVertices[VertexInstanceID]);
|
|
VertexInstanceToTriangles.RemoveReferenceFromKey(OldVertexInstanceID, Tris[0]);
|
|
TriangleVertexInstances[Tris[0]][PerimeterIndex] = VertexInstanceID;
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, Tris[0]);
|
|
}
|
|
else
|
|
{
|
|
TArray<FVertexInstanceID, TInlineAllocator<8>> PolygonVertexInstances = GetPolygonVertexInstances<TInlineAllocator<8>>(PolygonID);
|
|
check(PerimeterIndex < PolygonVertexInstances.Num());
|
|
FVertexInstanceID OldVertexInstanceID = PolygonVertexInstances[PerimeterIndex];
|
|
check(VertexInstanceVertices[OldVertexInstanceID] == VertexInstanceVertices[VertexInstanceID]);
|
|
|
|
for (const FTriangleID Tri : Tris)
|
|
{
|
|
for (FVertexInstanceID& VertexInstance : TriangleVertexInstances[Tri])
|
|
{
|
|
if (VertexInstance == OldVertexInstanceID)
|
|
{
|
|
VertexInstanceToTriangles.RemoveReferenceFromKey(OldVertexInstanceID, Tri);
|
|
VertexInstance = VertexInstanceID;
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, Tri);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::SetPolygonVertexInstances(const FPolygonID PolygonID, TArrayView<const FVertexInstanceID> VertexInstanceIDs)
|
|
{
|
|
RemovePolygonTriangles(PolygonID);
|
|
CreatePolygonTriangles(PolygonID, VertexInstanceIDs);
|
|
}
|
|
|
|
|
|
FPlane FMeshDescription::ComputePolygonPlane(TArrayView<const FVertexInstanceID> PerimeterVertexInstanceIDs) const
|
|
{
|
|
// NOTE: This polygon plane computation code is partially based on the implementation of "Newell's method" from Real-Time
|
|
// Collision Detection by Christer Ericson, published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc
|
|
|
|
// @todo mesheditor perf: For polygons that are just triangles, use a cross product to get the normal fast!
|
|
// @todo mesheditor perf: We could skip computing the plane distance when we only need the normal
|
|
// @todo mesheditor perf: We could cache these computed polygon normals; or just use the normal of the first three vertices' triangle if it is satisfactory in all cases
|
|
// @todo mesheditor: For non-planar polygons, the result can vary. Ideally this should use the actual polygon triangulation as opposed to the arbitrary triangulation used here.
|
|
|
|
FVector Centroid = FVector::ZeroVector;
|
|
FVector Normal = FVector::ZeroVector;
|
|
|
|
// Use 'Newell's Method' to compute a robust 'best fit' plane from the vertices of this polygon
|
|
for (int32 VertexNumberI = PerimeterVertexInstanceIDs.Num() - 1, VertexNumberJ = 0; VertexNumberJ < PerimeterVertexInstanceIDs.Num(); VertexNumberI = VertexNumberJ, VertexNumberJ++)
|
|
{
|
|
const FVertexID VertexIDI = PerimeterVertexInstanceIDs[VertexNumberI];
|
|
const FVector3f PositionI = VertexPositions[VertexInstanceVertices[VertexIDI]];
|
|
|
|
const FVertexID VertexIDJ = PerimeterVertexInstanceIDs[VertexNumberJ];
|
|
const FVector3f PositionJ = VertexPositions[VertexInstanceVertices[VertexIDJ]];
|
|
|
|
Centroid += PositionJ;
|
|
|
|
Normal.X += (PositionJ.Y - PositionI.Y) * (PositionI.Z + PositionJ.Z);
|
|
Normal.Y += (PositionJ.Z - PositionI.Z) * (PositionI.X + PositionJ.X);
|
|
Normal.Z += (PositionJ.X - PositionI.X) * (PositionI.Y + PositionJ.Y);
|
|
}
|
|
|
|
Normal.Normalize();
|
|
|
|
// Construct a plane from the normal and centroid
|
|
return FPlane(Normal, FVector::DotProduct(Centroid, Normal) / (float)PerimeterVertexInstanceIDs.Num());
|
|
}
|
|
|
|
|
|
FVector FMeshDescription::ComputePolygonNormal(TArrayView<const FVertexInstanceID> PerimeterVertexInstanceIDs) const
|
|
{
|
|
// @todo mesheditor: Polygon normals are now computed and cached when changes are made to a polygon.
|
|
// In theory, we can just return that cached value, but we need to check that there is nothing which relies on the value being correct before
|
|
// the cache is updated at the end of a modification.
|
|
const FPlane PolygonPlane = ComputePolygonPlane(PerimeterVertexInstanceIDs);
|
|
const FVector PolygonNormal(PolygonPlane.X, PolygonPlane.Y, PolygonPlane.Z);
|
|
return PolygonNormal;
|
|
}
|
|
|
|
|
|
/** Returns true if the triangle formed by the specified three positions has a normal that is facing the opposite direction of the reference normal */
|
|
static bool IsTriangleFlipped(const FVector ReferenceNormal, const FVector VertexPositionA, const FVector VertexPositionB, const FVector VertexPositionC)
|
|
{
|
|
const FVector TriangleNormal = FVector::CrossProduct(
|
|
VertexPositionC - VertexPositionA,
|
|
VertexPositionB - VertexPositionA).GetSafeNormal();
|
|
return (FVector::DotProduct(ReferenceNormal, TriangleNormal) <= 0.0f);
|
|
}
|
|
|
|
|
|
/** Given three direction vectors, indicates if A and B are on the same 'side' of Vec. */
|
|
static bool VectorsOnSameSide(const FVector& Vec, const FVector& A, const FVector& B, const float SameSideDotProductEpsilon)
|
|
{
|
|
const FVector CrossA = FVector::CrossProduct(Vec, A);
|
|
const FVector CrossB = FVector::CrossProduct(Vec, B);
|
|
float DotWithEpsilon = SameSideDotProductEpsilon + FVector::DotProduct(CrossA, CrossB);
|
|
return !FMath::IsNegativeFloat(DotWithEpsilon);
|
|
}
|
|
|
|
|
|
/** Util to see if P lies within triangle created by A, B and C. */
|
|
static bool PointInTriangle(const FVector& A, const FVector& B, const FVector& C, const FVector& P, const float InsideTriangleDotProductEpsilon)
|
|
{
|
|
// Cross product indicates which 'side' of the vector the point is on
|
|
// If its on the same side as the remaining vert for all edges, then its inside.
|
|
return (VectorsOnSameSide(B - A, P - A, C - A, InsideTriangleDotProductEpsilon) &&
|
|
VectorsOnSameSide(C - B, P - B, A - B, InsideTriangleDotProductEpsilon) &&
|
|
VectorsOnSameSide(A - C, P - C, B - C, InsideTriangleDotProductEpsilon));
|
|
}
|
|
|
|
|
|
void FMeshDescription::FindPolygonPerimeter(const FPolygonID PolygonID, TArrayView<FEdgeID> Edges) const
|
|
{
|
|
TArrayView<const FTriangleID> PolygonTris = PolygonToTriangles.Find<FTriangleID>(PolygonID);
|
|
check(Edges.Num() == PolygonTris.Num() + 2);
|
|
|
|
// Optimization for simple triangle
|
|
if (PolygonTris.Num() == 1)
|
|
{
|
|
for (int I = 0; I < 3; I++)
|
|
{
|
|
Edges[I] = TriangleEdges[PolygonTris[0]][I];
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Determine perimeter for arbitrary n-gon.
|
|
// @todo: This process can undoubtedly be optimized
|
|
|
|
// Build a set of all the perimeter edges
|
|
TArray<FEdgeID, TInlineAllocator<8>> PerimeterEdges;
|
|
TArray<int32, TInlineAllocator<8>> TriIndices;
|
|
|
|
for (int32 TriIndex = 0; TriIndex < PolygonTris.Num(); TriIndex++)
|
|
{
|
|
FTriangleID PolygonTri = PolygonTris[TriIndex];
|
|
|
|
TArrayView<const FEdgeID> TriEdges = TriangleEdges[PolygonTri];
|
|
for (FEdgeID EdgeID : TriEdges)
|
|
{
|
|
int32 EdgeIndex = PerimeterEdges.Find(EdgeID);
|
|
if (EdgeIndex != INDEX_NONE)
|
|
{
|
|
// If adding an edge which already exists, it must be an internal edge, so remove it again.
|
|
PerimeterEdges.RemoveAtSwap(EdgeIndex, 1, false);
|
|
TriIndices.RemoveAtSwap(EdgeIndex, 1, false);
|
|
}
|
|
else
|
|
{
|
|
PerimeterEdges.Add(EdgeID);
|
|
TriIndices.Add(TriIndex);
|
|
}
|
|
}
|
|
}
|
|
check(PerimeterEdges.Num() == PolygonTris.Num() + 2);
|
|
check(PerimeterEdges.Num() == TriIndices.Num());
|
|
|
|
// Reorder edges to be adjacent by ensuring there's a mutual vertex in consecutive edges
|
|
for (int32 EdgeIndex = 0; EdgeIndex < PerimeterEdges.Num() - 2; EdgeIndex++)
|
|
{
|
|
TArrayView<const FVertexID> EdgeVertexIDs = EdgeVertices[PerimeterEdges[EdgeIndex]];
|
|
for (int32 NextEdgeIndex = EdgeIndex + 1; NextEdgeIndex < PerimeterEdges.Num(); NextEdgeIndex++)
|
|
{
|
|
TArrayView<const FVertexID> NextEdgeVertexIDs = EdgeVertices[PerimeterEdges[NextEdgeIndex]];
|
|
if (EdgeVertexIDs[0] == NextEdgeVertexIDs[0] || EdgeVertexIDs[0] == NextEdgeVertexIDs[1] ||
|
|
EdgeVertexIDs[1] == NextEdgeVertexIDs[0] || EdgeVertexIDs[1] == NextEdgeVertexIDs[1])
|
|
{
|
|
if (NextEdgeIndex > EdgeIndex + 1)
|
|
{
|
|
Swap(PerimeterEdges[EdgeIndex + 1], PerimeterEdges[NextEdgeIndex]);
|
|
Swap(TriIndices[EdgeIndex + 1], TriIndices[NextEdgeIndex]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
check(EdgeVertices[PerimeterEdges.Last()][0] == EdgeVertices[PerimeterEdges[0]][0] || EdgeVertices[PerimeterEdges.Last()][0] == EdgeVertices[PerimeterEdges[0]][1] ||
|
|
EdgeVertices[PerimeterEdges.Last()][1] == EdgeVertices[PerimeterEdges[0]][0] || EdgeVertices[PerimeterEdges.Last()][1] == EdgeVertices[PerimeterEdges[0]][1]);
|
|
|
|
// Swap the winding order if incorrect
|
|
FEdgeID FirstEdge = PerimeterEdges[0];
|
|
FEdgeID SecondEdge = PerimeterEdges[1];
|
|
|
|
// Get the triangle which the first edge lies in
|
|
FTriangleID FirstTriangle = PolygonTris[TriIndices[0]];
|
|
int32 TriEdgeIndex = TriangleEdges[FirstTriangle].Find(FirstEdge);
|
|
|
|
// Get the end vertex of the edge as used in that triangle
|
|
FVertexID SecondVertex = TriangleVertices[FirstTriangle][(TriEdgeIndex + 1) % 3];
|
|
|
|
// If the second edge doesn't contain that end vertex, we need to reverse the order of the edges we just constructed
|
|
if (EdgeVertices[SecondEdge][0] != SecondVertex && EdgeVertices[SecondEdge][1] != SecondVertex)
|
|
{
|
|
for (int32 I = 0; I < PerimeterEdges.Num() / 2; I++)
|
|
{
|
|
Swap(PerimeterEdges[I], PerimeterEdges[PerimeterEdges.Num() - 1 - I]);
|
|
Swap(TriIndices[I], TriIndices[PerimeterEdges.Num() - 1 - I]);
|
|
}
|
|
}
|
|
|
|
for (int I = 0; I < PerimeterEdges.Num(); I++)
|
|
{
|
|
Edges[I] = PerimeterEdges[I];
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::FindPolygonPerimeter(TArrayView<const FTriangleID> Triangles, TArrayView<TTuple<int32, int32>> Result) const
|
|
{
|
|
// This constructs the perimeter indices for a polygon in the correct winding order from its constituent triangles
|
|
check(Result.Num() == Triangles.Num() + 2);
|
|
|
|
// Optimization for simple triangle
|
|
if (Triangles.Num() == 1)
|
|
{
|
|
// Return triangle index 0; edges 0, 1, 2
|
|
Result[0] = MakeTuple(0, 0);
|
|
Result[1] = MakeTuple(0, 1);
|
|
Result[2] = MakeTuple(0, 2);
|
|
return;
|
|
}
|
|
|
|
// Determine perimeter for arbitrary n-gon.
|
|
// @todo: This process can undoubtedly be optimized
|
|
|
|
// Build a set of all the perimeter edges
|
|
TArray<FEdgeID, TInlineAllocator<8>> PerimeterEdges;
|
|
TArray<TTuple<int32, int32>, TInlineAllocator<8>> Indices;
|
|
|
|
for (int32 TriIndex = 0; TriIndex < Triangles.Num(); TriIndex++)
|
|
{
|
|
FTriangleID PolygonTri = Triangles[TriIndex];
|
|
|
|
TArrayView<const FEdgeID> TriEdges = TriangleEdges[PolygonTri];
|
|
for (int32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++)
|
|
{
|
|
FEdgeID EdgeID = TriEdges[EdgeIndex];
|
|
int32 PerimeterIndex = PerimeterEdges.Find(EdgeID);
|
|
if (PerimeterIndex != INDEX_NONE)
|
|
{
|
|
// If adding an edge which already exists, it must be an internal edge, so remove it again.
|
|
PerimeterEdges.RemoveAtSwap(PerimeterIndex, 1, false);
|
|
Indices.RemoveAtSwap(PerimeterIndex, 1, false);
|
|
}
|
|
else
|
|
{
|
|
PerimeterEdges.Add(EdgeID);
|
|
Indices.Add(MakeTuple(TriIndex, EdgeIndex));
|
|
}
|
|
}
|
|
}
|
|
check(PerimeterEdges.Num() == Triangles.Num() + 2);
|
|
check(PerimeterEdges.Num() == Indices.Num());
|
|
|
|
// Reorder edges to be adjacent by ensuring there's a mutual vertex in consecutive edges
|
|
for (int32 EdgeIndex = 0; EdgeIndex < PerimeterEdges.Num() - 2; EdgeIndex++)
|
|
{
|
|
TArrayView<const FVertexID> EdgeVertexIDs = EdgeVertices[PerimeterEdges[EdgeIndex]];
|
|
for (int32 NextEdgeIndex = EdgeIndex + 1; NextEdgeIndex < PerimeterEdges.Num(); NextEdgeIndex++)
|
|
{
|
|
TArrayView<const FVertexID> NextEdgeVertexIDs = EdgeVertices[PerimeterEdges[NextEdgeIndex]];
|
|
if (EdgeVertexIDs[0] == NextEdgeVertexIDs[0] || EdgeVertexIDs[0] == NextEdgeVertexIDs[1] ||
|
|
EdgeVertexIDs[1] == NextEdgeVertexIDs[0] || EdgeVertexIDs[1] == NextEdgeVertexIDs[1])
|
|
{
|
|
if (NextEdgeIndex > EdgeIndex + 1)
|
|
{
|
|
Swap(PerimeterEdges[EdgeIndex + 1], PerimeterEdges[NextEdgeIndex]);
|
|
Swap(Indices[EdgeIndex + 1], Indices[NextEdgeIndex]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
check(EdgeVertices[PerimeterEdges.Last()][0] == EdgeVertices[PerimeterEdges[0]][0] || EdgeVertices[PerimeterEdges.Last()][0] == EdgeVertices[PerimeterEdges[0]][1] ||
|
|
EdgeVertices[PerimeterEdges.Last()][1] == EdgeVertices[PerimeterEdges[0]][0] || EdgeVertices[PerimeterEdges.Last()][1] == EdgeVertices[PerimeterEdges[0]][1]);
|
|
|
|
// Swap the winding order if incorrect
|
|
FEdgeID FirstEdge = PerimeterEdges[0];
|
|
FEdgeID SecondEdge = PerimeterEdges[1];
|
|
|
|
// Get the triangle which the first edge lies in
|
|
FTriangleID FirstTriangle = Triangles[Indices[0].Get<0>()];
|
|
int32 TriEdgeIndex = TriangleEdges[FirstTriangle].Find(FirstEdge);
|
|
|
|
// Get the end vertex of the edge as used in that triangle
|
|
FVertexID SecondVertex = TriangleVertices[FirstTriangle][(TriEdgeIndex + 1) % 3];
|
|
|
|
// If the second edge doesn't contain that end vertex, we need to reverse the order of the edges we just constructed
|
|
if (EdgeVertices[SecondEdge][0] != SecondVertex && EdgeVertices[SecondEdge][1] != SecondVertex)
|
|
{
|
|
for (int32 I = 0; I < Indices.Num() / 2; I++)
|
|
{
|
|
Swap(Indices[I], Indices[PerimeterEdges.Num() - 1 - I]);
|
|
}
|
|
}
|
|
|
|
for (int I = 0; I < Indices.Num(); I++)
|
|
{
|
|
Result[I] = Indices[I];
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::ComputePolygonTriangulation(const FPolygonID PolygonID)
|
|
{
|
|
TArrayView<const FTriangleID> TriangleIDs = PolygonToTriangles.Find<FTriangleID>(PolygonID);
|
|
|
|
// Not valid to call this on an untriangulated polygon - an untriangulated polygon is no longer valid at all.
|
|
check(TriangleIDs.Num() > 0);
|
|
|
|
// If polygon was already triangulated, and only has three vertices, no need to do anything here
|
|
if (TriangleIDs.Num() == 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the current perimeter vertex instances
|
|
TArray<FVertexInstanceID, TInlineAllocator<8>> PolygonVertexInstanceIDs = GetPolygonVertexInstances<TInlineAllocator<8>>(PolygonID);
|
|
|
|
// Remove existing triangles
|
|
RemovePolygonTriangles(PolygonID);
|
|
|
|
// Perform the triangulation
|
|
CreatePolygonTriangles(PolygonID, PolygonVertexInstanceIDs);
|
|
}
|
|
|
|
|
|
void FMeshDescription::RemovePolygonTriangles(const FPolygonID PolygonID)
|
|
{
|
|
TArrayView<const FTriangleID> TriangleIDs = PolygonToTriangles.Find<FTriangleID>(PolygonID);
|
|
const FPolygonGroupID PolygonGroupID = PolygonPolygonGroups[PolygonID];
|
|
|
|
// Remove currently configured triangles
|
|
TArray<FEdgeID> InternalEdgesToRemove;
|
|
InternalEdgesToRemove.Reserve(TriangleIDs.Num() - 1);
|
|
|
|
for (const FTriangleID TriangleID : TriangleIDs)
|
|
{
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
VertexInstanceToTriangles.RemoveReferenceFromKey(TriVertexInstanceIDs[Index], TriangleID);
|
|
|
|
const FEdgeID EdgeID = TriEdgeIDs[Index];
|
|
if (IsEdgeInternal(EdgeID))
|
|
{
|
|
InternalEdgesToRemove.Add(EdgeID);
|
|
}
|
|
|
|
EdgeToTriangles.RemoveReferenceFromKey(EdgeID, TriangleID);
|
|
}
|
|
|
|
PolygonGroupToTriangles.RemoveReferenceFromKey(PolygonGroupID, TriangleID);
|
|
TriangleElements->Get().Remove(TriangleID);
|
|
}
|
|
|
|
PolygonToTriangles.RemoveKey(PolygonID);
|
|
|
|
// Remove internal edges
|
|
for (const FEdgeID EdgeID : InternalEdgesToRemove)
|
|
{
|
|
for (const FVertexID EdgeVertexID : EdgeVertices[EdgeID])
|
|
{
|
|
VertexToEdges.RemoveReferenceFromKey(EdgeVertexID, EdgeID);
|
|
}
|
|
|
|
EdgeElements->Get().Remove(EdgeID);
|
|
EdgeToTriangles.RemoveKey(EdgeID);
|
|
}
|
|
}
|
|
|
|
void FMeshDescription::CreatePolygonTriangles(const FPolygonID PolygonID, TArrayView<const FVertexInstanceID> VertexInstanceIDs)
|
|
{
|
|
FPolygonGroupID PolygonGroupID = PolygonPolygonGroups[PolygonID];
|
|
|
|
// If perimeter only has 3 vertices, just add a single triangle and return
|
|
if (VertexInstanceIDs.Num() == 3)
|
|
{
|
|
const FTriangleID TriangleID = TriangleElements->Get().Add();
|
|
|
|
// Fill out triangle vertex instances
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
TriVertexInstanceIDs[0] = VertexInstanceIDs[0];
|
|
TriVertexInstanceIDs[1] = VertexInstanceIDs[1];
|
|
TriVertexInstanceIDs[2] = VertexInstanceIDs[2];
|
|
|
|
// Fill out triangle polygon group
|
|
TrianglePolygonGroups[TriangleID] = PolygonGroupID;
|
|
PolygonGroupToTriangles.AddReferenceToKey(PolygonGroupID, TriangleID);
|
|
|
|
// Fill out triangle polygon
|
|
TrianglePolygons[TriangleID] = PolygonID;
|
|
PolygonToTriangles.AddReferenceToKey(PolygonID, TriangleID);
|
|
|
|
TArrayView<FVertexID> TriVertexIDs = TriangleVertices[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
const FVertexInstanceID VertexInstanceID = TriVertexInstanceIDs[Index];
|
|
const FVertexInstanceID NextVertexInstanceID = TriVertexInstanceIDs[(Index == 2) ? 0 : Index + 1];
|
|
|
|
const FVertexID ThisVertexID = VertexInstanceVertices[VertexInstanceID];
|
|
const FVertexID NextVertexID = VertexInstanceVertices[NextVertexInstanceID];
|
|
|
|
TriVertexIDs[Index] = ThisVertexID;
|
|
|
|
FEdgeID EdgeID = GetVertexPairEdge(ThisVertexID, NextVertexID);
|
|
check(EdgeID != INDEX_NONE);
|
|
TriEdgeIDs[Index] = EdgeID;
|
|
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, TriangleID);
|
|
EdgeToTriangles.AddReferenceToKey(EdgeID, TriangleID);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// NOTE: This polygon triangulation code is partially based on the ear cutting algorithm described on
|
|
// page 497 of the book "Real-time Collision Detection", published in 2005.
|
|
|
|
// @todo mesheditor: Perhaps should always attempt to triangulate by splitting polygons along the shortest edge, for better determinism.
|
|
|
|
// First figure out the polygon normal. We need this to determine which triangles are convex, so that
|
|
// we can figure out which ears to clip
|
|
const FVector PolygonNormal = ComputePolygonNormal(VertexInstanceIDs);
|
|
|
|
// Make a simple linked list array of the previous and next vertex numbers, for each vertex number
|
|
// in the polygon. This will just save us having to iterate later on.
|
|
TArray<int32> PrevVertexNumbers;
|
|
TArray<int32> NextVertexNumbers;
|
|
TArray<FVector> PolyVertexPositions;
|
|
int32 PolygonVertexCount = VertexInstanceIDs.Num();
|
|
{
|
|
PrevVertexNumbers.SetNumUninitialized(PolygonVertexCount, false);
|
|
NextVertexNumbers.SetNumUninitialized(PolygonVertexCount, false);
|
|
PolyVertexPositions.SetNumUninitialized(PolygonVertexCount, false);
|
|
|
|
for (int32 VertexNumber = 0; VertexNumber < PolygonVertexCount; ++VertexNumber)
|
|
{
|
|
PrevVertexNumbers[VertexNumber] = VertexNumber - 1;
|
|
NextVertexNumbers[VertexNumber] = VertexNumber + 1;
|
|
|
|
PolyVertexPositions[VertexNumber] = VertexPositions[GetVertexInstanceVertex(VertexInstanceIDs[VertexNumber])];
|
|
}
|
|
PrevVertexNumbers[0] = PolygonVertexCount - 1;
|
|
NextVertexNumbers[PolygonVertexCount - 1] = 0;
|
|
}
|
|
|
|
int32 EarVertexNumber = 0;
|
|
int32 EarTestCount = 0;
|
|
for (int32 RemainingVertexCount = PolygonVertexCount; RemainingVertexCount >= 3; )
|
|
{
|
|
bool bIsEar = true;
|
|
|
|
// If we're down to only a triangle, just treat it as an ear. Also, if we've tried every possible candidate
|
|
// vertex looking for an ear, go ahead and just treat the current vertex as an ear. This can happen when
|
|
// vertices are colinear or other degenerate cases.
|
|
if (RemainingVertexCount > 3 && EarTestCount < RemainingVertexCount)
|
|
{
|
|
const FVector PrevVertexPosition = PolyVertexPositions[PrevVertexNumbers[EarVertexNumber]];
|
|
const FVector EarVertexPosition = PolyVertexPositions[EarVertexNumber];
|
|
const FVector NextVertexPosition = PolyVertexPositions[NextVertexNumbers[EarVertexNumber]];
|
|
|
|
// Figure out whether the potential ear triangle is facing the same direction as the polygon
|
|
// itself. If it's facing the opposite direction, then we're dealing with a concave triangle
|
|
// and we'll skip it for now.
|
|
if (!IsTriangleFlipped(PolygonNormal, PrevVertexPosition, EarVertexPosition, NextVertexPosition))
|
|
{
|
|
int32 TestVertexNumber = NextVertexNumbers[NextVertexNumbers[EarVertexNumber]];
|
|
|
|
do
|
|
{
|
|
// Test every other remaining vertex to make sure that it doesn't lie inside our potential ear
|
|
// triangle. If we find a vertex that's inside the triangle, then it cannot actually be an ear.
|
|
const FVector TestVertexPosition = PolyVertexPositions[TestVertexNumber];
|
|
if (PointInTriangle(PrevVertexPosition, EarVertexPosition, NextVertexPosition, TestVertexPosition, SMALL_NUMBER))
|
|
{
|
|
bIsEar = false;
|
|
break;
|
|
}
|
|
|
|
TestVertexNumber = NextVertexNumbers[TestVertexNumber];
|
|
|
|
} while (TestVertexNumber != PrevVertexNumbers[EarVertexNumber]);
|
|
}
|
|
else
|
|
{
|
|
bIsEar = false;
|
|
}
|
|
}
|
|
|
|
if (bIsEar)
|
|
{
|
|
// OK, we found an ear! Let's save this triangle in our output buffer.
|
|
// This will also create any missing internal edges.
|
|
{
|
|
// Add a new triangle
|
|
const FTriangleID TriangleID = TriangleElements->Get().Add();
|
|
|
|
// Fill out triangle vertex instances
|
|
TArrayView<FVertexInstanceID> TriVertexInstanceIDs = TriangleVertexInstances[TriangleID];
|
|
TriVertexInstanceIDs[0] = VertexInstanceIDs[PrevVertexNumbers[EarVertexNumber]];
|
|
TriVertexInstanceIDs[1] = VertexInstanceIDs[EarVertexNumber];
|
|
TriVertexInstanceIDs[2] = VertexInstanceIDs[NextVertexNumbers[EarVertexNumber]];
|
|
|
|
// Fill out triangle polygon group
|
|
TrianglePolygonGroups[TriangleID] = PolygonGroupID;
|
|
PolygonGroupToTriangles.AddReferenceToKey(PolygonGroupID, TriangleID);
|
|
|
|
// Fill out triangle polygon
|
|
TrianglePolygons[TriangleID] = PolygonID;
|
|
PolygonToTriangles.AddReferenceToKey(PolygonID, TriangleID);
|
|
|
|
TArrayView<FVertexID> TriVertexIDs = TriangleVertices[TriangleID];
|
|
TArrayView<FEdgeID> TriEdgeIDs = TriangleEdges[TriangleID];
|
|
|
|
for (int32 Index = 0; Index < 3; ++Index)
|
|
{
|
|
const FVertexInstanceID VertexInstanceID = TriVertexInstanceIDs[Index];
|
|
const FVertexInstanceID NextVertexInstanceID = TriVertexInstanceIDs[(Index == 2) ? 0 : Index + 1];
|
|
|
|
const FVertexID ThisVertexID = VertexInstanceVertices[VertexInstanceID];
|
|
const FVertexID NextVertexID = VertexInstanceVertices[NextVertexInstanceID];
|
|
|
|
TriVertexIDs[Index] = ThisVertexID;
|
|
|
|
FEdgeID EdgeID = GetVertexPairEdge(ThisVertexID, NextVertexID);
|
|
if (EdgeID == INDEX_NONE)
|
|
{
|
|
// This must be an internal edge (as perimeter edges will already be defined)
|
|
EdgeID = CreateEdge(ThisVertexID, NextVertexID);
|
|
}
|
|
TriEdgeIDs[Index] = EdgeID;
|
|
|
|
VertexInstanceToTriangles.AddReferenceToKey(VertexInstanceID, TriangleID);
|
|
EdgeToTriangles.AddReferenceToKey(EdgeID, TriangleID);
|
|
}
|
|
}
|
|
|
|
// Update our linked list. We're effectively cutting off the ear by pointing the ear vertex's neighbors to
|
|
// point at their next sequential neighbor, and reducing the remaining vertex count by one.
|
|
{
|
|
NextVertexNumbers[PrevVertexNumbers[EarVertexNumber]] = NextVertexNumbers[EarVertexNumber];
|
|
PrevVertexNumbers[NextVertexNumbers[EarVertexNumber]] = PrevVertexNumbers[EarVertexNumber];
|
|
--RemainingVertexCount;
|
|
}
|
|
|
|
// Move on to the previous vertex in the list, now that this vertex was cut
|
|
EarVertexNumber = PrevVertexNumbers[EarVertexNumber];
|
|
|
|
EarTestCount = 0;
|
|
}
|
|
else
|
|
{
|
|
// The vertex is not the ear vertex, because it formed a triangle that either had a normal which pointed in the opposite direction
|
|
// of the polygon, or at least one of the other polygon vertices was found to be inside the triangle. Move on to the next vertex.
|
|
EarVertexNumber = NextVertexNumbers[EarVertexNumber];
|
|
|
|
// Keep track of how many ear vertices we've tested, so that if we exhaust all remaining vertices, we can
|
|
// fall back to clipping the triangle and adding it to our mesh anyway. This is important for degenerate cases.
|
|
++EarTestCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FBoxSphereBounds FMeshDescription::GetBounds() const
|
|
{
|
|
FBoxSphereBounds BoundingBoxAndSphere;
|
|
|
|
FBox BoundingBox;
|
|
BoundingBox.Init();
|
|
|
|
for (const FVertexID VertexID : Vertices().GetElementIDs())
|
|
{
|
|
if (!IsVertexOrphaned(VertexID))
|
|
{
|
|
BoundingBox += VertexPositions[VertexID];
|
|
}
|
|
}
|
|
|
|
BoundingBox.GetCenterAndExtents(BoundingBoxAndSphere.Origin, BoundingBoxAndSphere.BoxExtent);
|
|
|
|
// Calculate the bounding sphere, using the center of the bounding box as the origin.
|
|
BoundingBoxAndSphere.SphereRadius = 0.0f;
|
|
|
|
for (const FVertexID VertexID : Vertices().GetElementIDs())
|
|
{
|
|
if (!IsVertexOrphaned(VertexID))
|
|
{
|
|
BoundingBoxAndSphere.SphereRadius = FMath::Max<FVector::FReal>((VertexPositions[VertexID] - BoundingBoxAndSphere.Origin).Size(), BoundingBoxAndSphere.SphereRadius);
|
|
}
|
|
}
|
|
|
|
return BoundingBoxAndSphere;
|
|
}
|
|
|
|
void FMeshDescription::TriangulateMesh()
|
|
{
|
|
// Perform triangulation directly into mesh polygons
|
|
for( const FPolygonID PolygonID : Polygons().GetElementIDs() )
|
|
{
|
|
ComputePolygonTriangulation( PolygonID );
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::SetNumUVChannels(const int32 NumUVChannels)
|
|
{
|
|
UVElements->SetNumChannels(NumUVChannels);
|
|
TriangleUVs = TriangleElements->Get().GetAttributes().RegisterIndexAttribute<FUVID[3]>(MeshAttribute::Triangle::UVIndex, NumUVChannels);
|
|
|
|
// Ensure every UV element channel has a UVCoordinate attribute
|
|
for (int32 Index = 0; Index < NumUVChannels; Index++)
|
|
{
|
|
UVElements->Get(Index).GetAttributes().RegisterAttribute(MeshAttribute::UV::UVCoordinate, 1, FVector2D::ZeroVector, EMeshAttributeFlags::Lerpable);
|
|
}
|
|
}
|
|
|
|
|
|
namespace MeshAttribute_
|
|
{
|
|
namespace Vertex
|
|
{
|
|
const FName CornerSharpness("CornerSharpness");
|
|
}
|
|
|
|
namespace VertexInstance
|
|
{
|
|
const FName TextureCoordinate("TextureCoordinate");
|
|
const FName Normal("Normal");
|
|
const FName Tangent("Tangent");
|
|
const FName BinormalSign("BinormalSign");
|
|
const FName Color("Color");
|
|
}
|
|
|
|
namespace Edge
|
|
{
|
|
const FName IsHard("IsHard");
|
|
const FName IsUVSeam("IsUVSeam");
|
|
const FName CreaseSharpness("CreaseSharpness");
|
|
}
|
|
|
|
namespace Polygon
|
|
{
|
|
const FName Normal("Normal");
|
|
const FName Tangent("Tangent");
|
|
const FName Binormal("Binormal");
|
|
const FName Center("Center");
|
|
}
|
|
|
|
namespace PolygonGroup
|
|
{
|
|
const FName ImportedMaterialSlotName("ImportedMaterialSlotName");
|
|
const FName EnableCollision("EnableCollision");
|
|
const FName CastShadow("CastShadow");
|
|
}
|
|
}
|
|
|
|
|
|
float FMeshDescription::GetPolygonCornerAngleForVertex(const FPolygonID PolygonID, const FVertexID VertexID) const
|
|
{
|
|
TArray<FVertexInstanceID> PolygonVertexInstanceIDs = GetPolygonVertexInstances(PolygonID);
|
|
|
|
// Lambda function which returns the inner angle at a given index on a polygon contour
|
|
auto GetContourAngle = [this](const TArray<FVertexInstanceID>& VertexInstanceIDs, const int32 ContourIndex)
|
|
{
|
|
const int32 NumVertices = VertexInstanceIDs.Num();
|
|
|
|
const int32 PrevIndex = (ContourIndex + NumVertices - 1) % NumVertices;
|
|
const int32 NextIndex = (ContourIndex + 1) % NumVertices;
|
|
|
|
const FVertexID PrevVertexID = GetVertexInstanceVertex(VertexInstanceIDs[PrevIndex]);
|
|
const FVertexID ThisVertexID = GetVertexInstanceVertex(VertexInstanceIDs[ContourIndex]);
|
|
const FVertexID NextVertexID = GetVertexInstanceVertex(VertexInstanceIDs[NextIndex]);
|
|
|
|
const FVector PrevVertexPosition = VertexPositions[PrevVertexID];
|
|
const FVector ThisVertexPosition = VertexPositions[ThisVertexID];
|
|
const FVector NextVertexPosition = VertexPositions[NextVertexID];
|
|
|
|
const FVector Direction1 = (PrevVertexPosition - ThisVertexPosition).GetSafeNormal();
|
|
const FVector Direction2 = (NextVertexPosition - ThisVertexPosition).GetSafeNormal();
|
|
|
|
return FMath::Acos(FVector::DotProduct(Direction1, Direction2));
|
|
};
|
|
|
|
auto IsVertexInstancedFromThisVertex = [this, VertexID](const FVertexInstanceID VertexInstanceID)
|
|
{
|
|
return this->GetVertexInstanceVertex(VertexInstanceID) == VertexID;
|
|
};
|
|
|
|
// First look for the vertex instance in the perimeter
|
|
int32 ContourIndex = PolygonVertexInstanceIDs.IndexOfByPredicate(IsVertexInstancedFromThisVertex);
|
|
if (ContourIndex != INDEX_NONE)
|
|
{
|
|
// Return the internal angle if found
|
|
return GetContourAngle(PolygonVertexInstanceIDs, ContourIndex);
|
|
}
|
|
|
|
// Found nothing; return 0
|
|
return 0.0f;
|
|
}
|
|
|
|
FBox FMeshDescription::ComputeBoundingBox() const
|
|
{
|
|
FBox BoundingBox(ForceInit);
|
|
|
|
for (const FVertexID VertexID : Vertices().GetElementIDs())
|
|
{
|
|
BoundingBox += VertexPositions[VertexID];
|
|
}
|
|
|
|
return BoundingBox;
|
|
}
|
|
|
|
|
|
void FMeshDescription::ReverseTriangleFacing(const FTriangleID TriangleID)
|
|
{
|
|
TArrayView<FVertexInstanceID> TriVertexInstances = TriangleVertexInstances[TriangleID];
|
|
TArrayView<FVertexID> TriVertices = TriangleVertices[TriangleID];
|
|
TArrayView<FEdgeID> TriEdges = TriangleEdges[TriangleID];
|
|
Swap(TriVertexInstances[0], TriVertexInstances[1]);
|
|
Swap(TriVertices[0], TriVertices[1]);
|
|
Swap(TriEdges[0], TriEdges[1]);
|
|
}
|
|
|
|
|
|
void FMeshDescription::ReversePolygonFacing(const FPolygonID PolygonID)
|
|
{
|
|
// Build a reverse perimeter
|
|
TArray<FVertexInstanceID> Contour = GetPolygonVertexInstances(PolygonID);
|
|
for (int32 i = 0; i < Contour.Num() / 2; ++i)
|
|
{
|
|
Swap(Contour[i], Contour[Contour.Num() - i - 1]);
|
|
}
|
|
|
|
RemovePolygonTriangles(PolygonID);
|
|
CreatePolygonTriangles(PolygonID, Contour);
|
|
}
|
|
|
|
|
|
void FMeshDescription::ReverseAllPolygonFacing()
|
|
{
|
|
// Perform triangulation directly into mesh polygons
|
|
for (const FPolygonID PolygonID : Polygons().GetElementIDs())
|
|
{
|
|
ReversePolygonFacing(PolygonID);
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshDescription::RemapPolygonGroups(const TMap<FPolygonGroupID, FPolygonGroupID>& Remap)
|
|
{
|
|
TPolygonGroupAttributesRef<FName> PolygonGroupNames = PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute_::PolygonGroup::ImportedMaterialSlotName);
|
|
|
|
struct FOldPolygonGroupData
|
|
{
|
|
FName Name;
|
|
TArray<FTriangleID> Triangles;
|
|
TArray<FPolygonID> Polygons;
|
|
};
|
|
|
|
TMap<FPolygonGroupID, FOldPolygonGroupData> OldData;
|
|
for (const FPolygonGroupID PolygonGroupID : PolygonGroups().GetElementIDs())
|
|
{
|
|
if (!Remap.Contains(PolygonGroupID) || PolygonGroupID == Remap[PolygonGroupID])
|
|
{
|
|
//No need to change this one
|
|
continue;
|
|
}
|
|
FOldPolygonGroupData& PolygonGroupData = OldData.FindOrAdd(PolygonGroupID);
|
|
PolygonGroupData.Name = PolygonGroupNames[PolygonGroupID];
|
|
PolygonGroupData.Triangles = PolygonGroupToTriangles.Find<FTriangleID>(PolygonGroupID);
|
|
PolygonGroupData.Polygons = PolygonGroupToPolygons.Find<FPolygonID>(PolygonGroupID);
|
|
PolygonGroupElements->Get().Remove(PolygonGroupID);
|
|
PolygonGroupToPolygons.RemoveKey(PolygonGroupID);
|
|
PolygonGroupToTriangles.RemoveKey(PolygonGroupID);
|
|
}
|
|
|
|
for (auto Kvp : OldData)
|
|
{
|
|
FPolygonGroupID GroupID = Kvp.Key;
|
|
FPolygonGroupID ToGroupID = Remap[GroupID];
|
|
if (!PolygonGroups().IsValid(ToGroupID))
|
|
{
|
|
CreatePolygonGroupWithID(ToGroupID);
|
|
}
|
|
PolygonGroupNames[ToGroupID] = Kvp.Value.Name;
|
|
|
|
for (const FTriangleID TriangleID : Kvp.Value.Triangles)
|
|
{
|
|
TrianglePolygonGroups[TriangleID] = ToGroupID;
|
|
PolygonGroupToTriangles.AddReferenceToKey(ToGroupID, TriangleID);
|
|
}
|
|
|
|
for (const FPolygonID PolygonID : Kvp.Value.Polygons)
|
|
{
|
|
PolygonPolygonGroups[PolygonID] = ToGroupID;
|
|
PolygonGroupToPolygons.AddReferenceToKey(ToGroupID, PolygonID);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
|
|
void FMeshDescriptionBulkData::Serialize( FArchive& Ar, UObject* Owner )
|
|
{
|
|
Ar.UsingCustomVersion( FEditorObjectVersion::GUID );
|
|
Ar.UsingCustomVersion( FReleaseObjectVersion::GUID );
|
|
Ar.UsingCustomVersion( FEnterpriseObjectVersion::GUID );
|
|
Ar.UsingCustomVersion( FUE5MainStreamObjectVersion::GUID );
|
|
|
|
// Make sure to serialize only actual data
|
|
if (Ar.ShouldSkipBulkData() || Ar.IsObjectReferenceCollector())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshDescriptionBulkData::Serialize);
|
|
|
|
if ( Ar.IsTransacting() )
|
|
{
|
|
// If transacting, keep these members alive the other side of an undo, otherwise their values will get lost
|
|
CustomVersions.Serialize( Ar );
|
|
Ar << bBulkDataUpdated;
|
|
}
|
|
else
|
|
{
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
// If the bulk data hasn't been updated since this was loaded, there's a possibility that it has old versioning.
|
|
// Explicitly load and resave the FMeshDescription so that its version is in sync with the FMeshDescriptionBulkData.
|
|
if( !bBulkDataUpdated )
|
|
{
|
|
const FGuid OriginalGuid = Guid;
|
|
FGuid OriginalHash;
|
|
if (!bGuidIsHash)
|
|
{
|
|
OriginalHash = GetHash();
|
|
}
|
|
|
|
FMeshDescription MeshDescription;
|
|
LoadMeshDescription( MeshDescription );
|
|
SaveMeshDescription( MeshDescription );
|
|
|
|
// Maintain original guid in case the hash is the same to avoid invalidating
|
|
// all the data already built using this guid.
|
|
if (!bGuidIsHash && OriginalHash == GetHash())
|
|
{
|
|
Guid = OriginalGuid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bSerializedOldDataTypes = false;
|
|
FByteBulkData TempBulkData;
|
|
if (Ar.IsLoading() && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::MeshDescriptionVirtualization)
|
|
{
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
// Serialize the old BulkData format and mark that we require a conversion after the guid has been serialized
|
|
TempBulkData.Serialize(Ar, Owner);
|
|
bSerializedOldDataTypes = true;
|
|
#else
|
|
BulkData.Serialize(Ar, Owner);
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
}
|
|
else
|
|
{
|
|
#if !UE_USE_VIRTUALBULKDATA && UE_VBD_TO_OLD_BULKDATA_PATH
|
|
if (Ar.IsLoading() && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::DisabledVirtualization)
|
|
{
|
|
UE::Virtualization::FByteVirtualizedBulkData TempVirtualBulkData;
|
|
TempVirtualBulkData.Serialize(Ar, Owner);
|
|
TempVirtualBulkData.ConvertToOldBulkData(BulkData);
|
|
}
|
|
else
|
|
#endif // !UE_USE_VIRTUALBULKDATA && UE_VBD_TO_OLD_BULKDATA_PATH
|
|
{
|
|
BulkData.Serialize(Ar, Owner);
|
|
}
|
|
}
|
|
|
|
if( Ar.IsLoading() && Ar.CustomVer( FEditorObjectVersion::GUID ) < FEditorObjectVersion::MeshDescriptionBulkDataGuid )
|
|
{
|
|
FPlatformMisc::CreateGuid( Guid );
|
|
}
|
|
else
|
|
{
|
|
Ar << Guid;
|
|
}
|
|
|
|
// MeshDescriptionBulkData contains a bGuidIsHash so we can benefit from DDC caching.
|
|
if( Ar.IsLoading() && Ar.CustomVer( FEnterpriseObjectVersion::GUID ) < FEnterpriseObjectVersion::MeshDescriptionBulkDataGuidIsHash )
|
|
{
|
|
bGuidIsHash = false;
|
|
}
|
|
else
|
|
{
|
|
Ar << bGuidIsHash;
|
|
}
|
|
|
|
if (!Ar.IsTransacting() && Ar.IsLoading())
|
|
{
|
|
// If loading, take a copy of the package custom version container, so it can be applied when unpacking
|
|
// MeshDescription from the bulk data.
|
|
CustomVersions = BulkData.GetCustomVersions(Ar);
|
|
}
|
|
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
// Needs to be after the guid is serialized
|
|
if (bSerializedOldDataTypes)
|
|
{
|
|
BulkData.CreateFromBulkData(TempBulkData, Guid);
|
|
}
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
}
|
|
|
|
|
|
void FMeshDescriptionBulkData::SaveMeshDescription( FMeshDescription& MeshDescription )
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshDescriptionBulkData::SaveMeshDescription);
|
|
|
|
#if WITH_EDITOR
|
|
FRWScopeLock ScopeLock(BulkDataLock, SLT_Write);
|
|
#endif
|
|
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
BulkData.Reset();
|
|
#else
|
|
BulkData.RemoveBulkData();
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
|
|
if( !MeshDescription.IsEmpty() )
|
|
{
|
|
const bool bIsPersistent = true;
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
UE::Virtualization::FVirtualizedBulkDataWriter Ar(BulkData, bIsPersistent);
|
|
#else
|
|
FBulkDataWriter Ar(BulkData, bIsPersistent);
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
|
|
Ar << MeshDescription;
|
|
|
|
// Preserve CustomVersions at save time so we can reuse the same ones when reloading direct from memory
|
|
CustomVersions = Ar.GetCustomVersions();
|
|
}
|
|
|
|
if (bGuidIsHash)
|
|
{
|
|
UseHashAsGuid();
|
|
}
|
|
else
|
|
{
|
|
FPlatformMisc::CreateGuid( Guid );
|
|
}
|
|
|
|
// Mark the MeshDescriptionBulkData as having been updated.
|
|
// This means we know that its version is up-to-date.
|
|
bBulkDataUpdated = true;
|
|
}
|
|
|
|
void FMeshDescriptionBulkData::LoadMeshDescription( FMeshDescription& MeshDescription)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshDescriptionBulkData::LoadMeshDescription);
|
|
|
|
MeshDescription.Empty();
|
|
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
if (BulkData.GetPayloadSize() > 0)
|
|
{
|
|
#if WITH_EDITOR
|
|
// TODO: Remove this once VirtualizedBulkData can be shown to be thread safe
|
|
// A lock is required so we can clone the mesh description from multiple threads
|
|
FRWScopeLock ScopeLock(BulkDataLock, SLT_Write);
|
|
#endif //WITH_EDITOR
|
|
|
|
const bool bIsPersistent = true;
|
|
UE::Virtualization::FVirtualizedBulkDataReader Ar(BulkData, bIsPersistent);
|
|
|
|
// Propagate the custom version information from the package to the bulk data, so that the MeshDescription
|
|
// is serialized with the same versioning.
|
|
Ar.SetCustomVersions(CustomVersions);
|
|
Ar << MeshDescription;
|
|
|
|
BulkData.UnloadData();
|
|
}
|
|
#else
|
|
if (BulkData.GetBulkDataSize() > 0)
|
|
{
|
|
// Get a lock on the bulk data and read it into the mesh description
|
|
#if WITH_EDITOR
|
|
// A lock is required so we can clone the mesh description from multiple threads
|
|
FRWScopeLock ScopeLock(BulkDataLock, SLT_Write);
|
|
|
|
// This allows any thread to be able to clone a mesh description directly
|
|
// from disk so we can unload bulk data from memory.
|
|
bool bHasBeenLoadedFromFileReader = false;
|
|
if (BulkData.IsAsyncLoadingComplete() && !BulkData.IsBulkDataLoaded())
|
|
{
|
|
// This will return false under -game mode for now because we're not allowed to load bulk data outside of EDL.
|
|
bHasBeenLoadedFromFileReader = BulkData.LoadBulkDataWithFileReader();
|
|
}
|
|
#endif //WITH_EDITOR
|
|
// This is in a scope because the FBulkDataReader need to be destroyed in order
|
|
// to unlock the BulkData and allow UnloadBulkData to actually do its job.
|
|
{
|
|
const bool bIsPersistent = true;
|
|
FBulkDataReader Ar(BulkData, bIsPersistent);
|
|
|
|
// Propagate the custom version information from the package to the bulk data, so that the MeshDescription
|
|
// is serialized with the same versioning.
|
|
Ar.SetCustomVersions( CustomVersions );
|
|
Ar << MeshDescription;
|
|
} // Unlock bulk data when we leave scope
|
|
|
|
#if WITH_EDITOR
|
|
// Throw away the bulk data allocation only in the case we can safely reload it from disk
|
|
// and if BulkData.LoadBulkDataWithFileReader() is allowed to work from any thread.
|
|
// This saves a significant amount of memory during map loading of Nanite Meshes.
|
|
if (bHasBeenLoadedFromFileReader)
|
|
{
|
|
verify(BulkData.UnloadBulkData());
|
|
}
|
|
#endif //WITH_EDITOR
|
|
}
|
|
#endif // UE_USE_VIRTUALBULKDATA
|
|
}
|
|
|
|
void FMeshDescriptionBulkData::Empty()
|
|
{
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
BulkData.Reset();
|
|
#else
|
|
BulkData.RemoveBulkData();
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
}
|
|
|
|
FString FMeshDescriptionBulkData::GetIdString() const
|
|
{
|
|
FString GuidString = Guid.ToString();
|
|
if (bGuidIsHash)
|
|
{
|
|
GuidString += TEXT("X");
|
|
}
|
|
return GuidString;
|
|
}
|
|
|
|
FGuid FMeshDescriptionBulkData::GetHash() const
|
|
{
|
|
if (BulkData.GetBulkDataSize() > 0)
|
|
{
|
|
#if UE_USE_VIRTUALBULKDATA
|
|
return BulkData.GetPayloadId().ToGuid();
|
|
#else
|
|
uint32 Hash[5] = {};
|
|
|
|
const void* Buffer = BulkData.LockReadOnly();
|
|
FSHA1::HashBuffer(Buffer, BulkData.GetBulkDataSize(), (uint8*)Hash);
|
|
BulkData.Unlock();
|
|
|
|
return FGuid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]);
|
|
#endif //UE_USE_VIRTUALBULKDATA
|
|
}
|
|
|
|
return FGuid();
|
|
}
|
|
|
|
void FMeshDescriptionBulkData::UseHashAsGuid()
|
|
{
|
|
if (BulkData.GetBulkDataSize() > 0)
|
|
{
|
|
bGuidIsHash = true;
|
|
|
|
Guid = GetHash();
|
|
}
|
|
else
|
|
{
|
|
Guid.Invalidate();
|
|
}
|
|
}
|
|
|
|
#endif // #if WITH_EDITORONLY_DATA
|
|
|