Files
UnrealEngineUWP/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp
Richard TalbotWatkin e74a1e7fc4 - Deprecated FRawMesh as source model serialization format for static meshes.
- MeshDescription now held as compressed bulk data and unpacked on demand. This is managed by FMeshDescriptionBulkData.
- Made RawMesh accessors in StaticMesh responsible for performing legacy conversion from MeshDescription if necessary.
- Added FBulkDataReader/FBulkDataWriter for serializing bulk data to/from archives.
- Added FUntypedBulkData::UnloadBulkData() for releasing the bulk data allocation without invalidating it (so it can be reloaded when necessary). Editor only.
- Renamed StaticMesh MeshDescription methods (now GetMeshDescription(), CommitMeshDescription() etc).
- Removed unnecessary mesh description attributes from StaticMesh: those which are used only by Editable Mesh, and transient attributes which are automatically generated when needed for building vertex tangent space.
- Slight change to FAttributesSetBase::RegisterAttribute(): if the attribute specified already exists, it will be amended to adopt the type, passed flags and number of indices. If only the number of indices changes, any existing data will be preserved if possible.
- Added TMeshAttributesRef::Copy() to copy an entire attributes array from one name/index to another.
- Changed implementation of TMeshAttributesRef/TMeshAttributesView to provide a const ref/view if the template type parameter is const. Added TMeshAttributesConstRef/TMeshAttributesConstView as an alias for backwards compatibility.
- Added FMeshDescription::IsEmpty() for determining whether a mesh description has any content or not.
- Removed versioning GUID for mesh description: this should be handled by each mesh description client now.

#rb Alexis.Matte

[CL 4644791 by Richard TalbotWatkin in Dev-Editor branch]
2018-12-10 09:29:08 -05:00

1409 lines
60 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "MeshMergeHelpers.h"
#include "Engine/MapBuildDataRegistry.h"
#include "Engine/MeshMerging.h"
#include "MaterialOptions.h"
#include "MeshDescription.h"
#include "MeshAttributes.h"
#include "MeshAttributeArray.h"
#include "MeshDescriptionOperations.h"
#include "Misc/PackageName.h"
#include "MaterialUtilities.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/SplineMeshComponent.h"
#include "Components/SkinnedMeshComponent.h"
#include "Rendering/SkeletalMeshModel.h"
#include "SkeletalMeshTypes.h"
#include "SkeletalRenderPublic.h"
#include "UObject/UObjectBaseUtility.h"
#include "UObject/Package.h"
#include "Materials/Material.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "HierarchicalLODUtilitiesModule.h"
#include "MeshMergeData.h"
#include "IHierarchicalLODUtilities.h"
#include "Engine/MeshMergeCullingVolume.h"
#include "Landscape.h"
#include "LandscapeProxy.h"
#include "Editor.h"
#include "Engine/StaticMesh.h"
#include "PhysicsEngine/ConvexElem.h"
#include "PhysicsEngine/BodySetup.h"
#include "MeshUtilities.h"
#include "ImageUtils.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "IMeshReductionManagerModule.h"
#include "LayoutUV.h"
#include "Components/InstancedStaticMeshComponent.h"
//DECLARE_LOG_CATEGORY_CLASS(LogMeshMerging, Verbose, All);
void FMeshMergeHelpers::ExtractSections(const UStaticMeshComponent* Component, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
const UStaticMesh* StaticMesh = Component->GetStaticMesh();
TArray<FName> MaterialSlotNames;
for (const FStaticMaterial& StaticMaterial : StaticMesh->StaticMaterials)
{
#if WITH_EDITOR
MaterialSlotNames.Add(StaticMaterial.ImportedMaterialSlotName);
#else
MaterialSlotNames.Add(StaticMaterial.MaterialSlotName);
#endif
}
const bool bMirrored = Component->GetComponentToWorld().GetDeterminant() < 0.f;
for (const FStaticMeshSection& MeshSection : StaticMesh->RenderData->LODResources[LODIndex].Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = Component->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
// Populate section data
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialIndex = MeshSection.MaterialIndex;
SectionInfo.MaterialSlotName = MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] : NAME_None;
SectionInfo.StartIndex = MeshSection.FirstIndex / 3;
SectionInfo.EndIndex = SectionInfo.StartIndex + MeshSection.NumTriangles;
// In case the object is mirrored the material indices/vertex data will be reversed in place, so we need to adjust the sections accordingly
if (bMirrored)
{
const uint32 NumTriangles = StaticMesh->RenderData->LODResources[LODIndex].GetNumTriangles();
SectionInfo.StartIndex = NumTriangles - SectionInfo.EndIndex;
SectionInfo.EndIndex = SectionInfo.StartIndex + MeshSection.NumTriangles;
}
if (MeshSection.bEnableCollision)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bEnableCollision));
}
if (MeshSection.bCastShadow && Component->CastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bCastShadow));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExtractSections(const USkeletalMeshComponent* Component, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
FSkeletalMeshModel* Resource = Component->SkeletalMesh->GetImportedModel();
checkf(Resource->LODModels.IsValidIndex(LODIndex), TEXT("Invalid LOD Index"));
TArray<FName> MaterialSlotNames = Component->GetMaterialSlotNames();
const FSkeletalMeshLODModel& Model = Resource->LODModels[LODIndex];
for (const FSkelMeshSection& MeshSection : Model.Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = Component->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialSlotName = MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] : NAME_None;
if (MeshSection.bCastShadow && Component->CastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bCastShadow));
}
if (MeshSection.bRecomputeTangent)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bRecomputeTangent));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExtractSections(const UStaticMesh* StaticMesh, int32 LODIndex, TArray<FSectionInfo>& OutSections)
{
static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
for (const FStaticMeshSection& MeshSection : StaticMesh->RenderData->LODResources[LODIndex].Sections)
{
// Retrieve material for this section
UMaterialInterface* StoredMaterial = StaticMesh->GetMaterial(MeshSection.MaterialIndex);
// Make sure the resource actual exists, otherwise use default material
StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;
// Populate section data
FSectionInfo SectionInfo;
SectionInfo.Material = StoredMaterial;
SectionInfo.MaterialIndex = MeshSection.MaterialIndex;
#if WITH_EDITOR
SectionInfo.MaterialSlotName = StaticMesh->StaticMaterials.IsValidIndex(MeshSection.MaterialIndex) ? StaticMesh->StaticMaterials[MeshSection.MaterialIndex].ImportedMaterialSlotName : NAME_None;
#else
SectionInfo.MaterialSlotName = StaticMesh->StaticMaterials.IsValidIndex(MeshSection.MaterialIndex) ? StaticMesh->StaticMaterials[MeshSection.MaterialIndex].MaterialSlotName : NAME_None;
#endif
if (MeshSection.bEnableCollision)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bEnableCollision));
}
if (MeshSection.bCastShadow)
{
SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bCastShadow));
}
OutSections.Add(SectionInfo);
}
}
void FMeshMergeHelpers::ExpandInstances(const UInstancedStaticMeshComponent* InInstancedStaticMeshComponent, FMeshDescription& InOutRawMesh, TArray<FSectionInfo>& InOutSections)
{
FMeshDescription CombinedRawMesh;
for(const FInstancedStaticMeshInstanceData& InstanceData : InInstancedStaticMeshComponent->PerInstanceSMData)
{
FMeshDescription InstanceRawMesh = InOutRawMesh;
FMeshMergeHelpers::TransformRawMeshVertexData(FTransform(InstanceData.Transform), InstanceRawMesh);
FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh);
}
InOutRawMesh = CombinedRawMesh;
}
void FMeshMergeHelpers::RetrieveMesh(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, FMeshDescription& RawMesh, bool bPropagateVertexColours)
{
const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
const FStaticMeshSourceModel& StaticMeshModel = StaticMesh->SourceModels[LODIndex];
const bool bIsSplineMeshComponent = StaticMeshComponent->IsA<USplineMeshComponent>();
// Imported meshes will have a valid mesh description
const bool bImportedMesh = StaticMesh->IsMeshDescriptionValid(LODIndex);
// Export the raw mesh data using static mesh render data
ExportStaticMeshLOD(StaticMesh->RenderData->LODResources[LODIndex], RawMesh, StaticMesh->StaticMaterials);
// Make sure the raw mesh is not irreparably malformed.
if (RawMesh.VertexInstances().Num() <= 0)
{
return;
}
// Use build settings from base mesh for LOD entries that was generated inside Editor.
const FMeshBuildSettings& BuildSettings = bImportedMesh ? StaticMeshModel.BuildSettings : StaticMesh->SourceModels[0].BuildSettings;
// Transform raw mesh to world space
FTransform ComponentToWorldTransform = StaticMeshComponent->GetComponentTransform();
// Handle spline mesh deformation
if (bIsSplineMeshComponent)
{
const USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(StaticMeshComponent);
// Deform raw mesh data according to the Spline Mesh Component's data
PropagateSplineDeformationToRawMesh(SplineMeshComponent, RawMesh);
}
// If specified propagate painted vertex colors into our raw mesh
if (bPropagateVertexColours)
{
PropagatePaintedColorsToRawMesh(StaticMeshComponent, LODIndex, RawMesh);
}
// Transform raw mesh vertex data by the Static Mesh Component's component to world transformation
TransformRawMeshVertexData(ComponentToWorldTransform, RawMesh);
if (RawMesh.VertexInstances().Num() <= 0)
{
return;
}
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
uint32 TangentOptions = FMeshDescriptionOperations::ETangentOptions::BlendOverlappingNormals;
if (BuildSettings.bRemoveDegenerates)
{
// If removing degenerate triangles, ignore them when computing tangents.
TangentOptions |= FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles;
}
FMeshDescriptionOperations::RecomputeNormalsAndTangentsIfNeeded(RawMesh, (FMeshDescriptionOperations::ETangentOptions)TangentOptions, BuildSettings.bUseMikkTSpace);
}
void FMeshMergeHelpers::RetrieveMesh(USkeletalMeshComponent* SkeletalMeshComponent, int32 LODIndex, FMeshDescription& RawMesh, bool bPropagateVertexColours)
{
FSkeletalMeshModel* Resource = SkeletalMeshComponent->SkeletalMesh->GetImportedModel();
if (Resource->LODModels.IsValidIndex(LODIndex))
{
FSkeletalMeshLODInfo& SrcLODInfo = *(SkeletalMeshComponent->SkeletalMesh->GetLODInfo(LODIndex));
// Get the CPU skinned verts for this LOD
TArray<FFinalSkinVertex> FinalVertices;
SkeletalMeshComponent->GetCPUSkinnedVertices(FinalVertices, LODIndex);
FSkeletalMeshLODModel& LODModel = Resource->LODModels[LODIndex];
const int32 NumSections = LODModel.Sections.Num();
//Empty the raw mesh
RawMesh.Empty();
TVertexAttributesRef<FVector> VertexPositions = RawMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TEdgeAttributesRef<bool> EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesRef<float> EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
int32 TotalTriangles = 0;
int32 TotalCorners = 0;
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
TotalTriangles += SkelMeshSection.NumTriangles;
}
TotalCorners = TotalTriangles * 3;
RawMesh.ReserveNewVertices(FinalVertices.Num());
RawMesh.ReserveNewPolygons(TotalTriangles);
RawMesh.ReserveNewVertexInstances(TotalCorners);
RawMesh.ReserveNewEdges(TotalCorners);
// Copy skinned vertex positions
for (int32 VertIndex = 0; VertIndex < FinalVertices.Num(); ++VertIndex)
{
const FVertexID VertexID = RawMesh.CreateVertex();
VertexPositions[VertexID] = FinalVertices[VertIndex].Position;
}
VertexInstanceUVs.SetNumIndices(MAX_TEXCOORDS);
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
const int32 NumWedges = SkelMeshSection.NumTriangles * 3;
//Create the polygon group ID
int32 SectionMaterialIndex = SkelMeshSection.MaterialIndex;
int32 MaterialIndex = SectionMaterialIndex;
// use the remapping of material indices for all LODs besides the base LOD
if (LODIndex > 0 && SrcLODInfo.LODMaterialMap.IsValidIndex(SkelMeshSection.MaterialIndex))
{
MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SkelMeshSection.MaterialIndex], 0, SkeletalMeshComponent->SkeletalMesh->Materials.Num());
}
FName ImportedMaterialSlotName = SkeletalMeshComponent->SkeletalMesh->Materials[MaterialIndex].ImportedMaterialSlotName;
const FPolygonGroupID SectionPolygonGroupID(SectionMaterialIndex);
if (!RawMesh.IsPolygonGroupValid(SectionPolygonGroupID))
{
RawMesh.CreatePolygonGroupWithID(SectionPolygonGroupID);
PolygonGroupImportedMaterialSlotNames[SectionPolygonGroupID] = ImportedMaterialSlotName;
}
int32 WedgeIndex = 0;
for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < SkelMeshSection.NumTriangles; ++SectionTriangleIndex)
{
FVertexID VertexIndexes[3];
FVertexInstanceID VertexInstanceIDs[3];
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex, ++WedgeIndex)
{
const int32 VertexIndexForWedge = LODModel.IndexBuffer[SkelMeshSection.BaseIndex + WedgeIndex];
VertexIndexes[CornerIndex] = FVertexID(VertexIndexForWedge);
FVertexInstanceID VertexInstanceID = RawMesh.CreateVertexInstance(VertexIndexes[CornerIndex]);
VertexInstanceIDs[CornerIndex] = VertexInstanceID;
const FSoftSkinVertex& SoftVertex = SkelMeshSection.SoftVertices[VertexIndexForWedge - SkelMeshSection.BaseVertexIndex];
const FFinalSkinVertex& SkinnedVertex = FinalVertices[VertexIndexForWedge];
//Set NTBs
const FVector TangentX = SkinnedVertex.TangentX.ToFVector();
const FVector TangentZ = SkinnedVertex.TangentZ.ToFVector();
//@todo: do we need to inverse the sign between skeletalmesh and staticmesh, the old code was doing so.
const float TangentYSign = SkinnedVertex.TangentZ.ToFVector4().W;
VertexInstanceTangents[VertexInstanceID] = TangentX;
VertexInstanceBinormalSigns[VertexInstanceID] = TangentYSign;
VertexInstanceNormals[VertexInstanceID] = TangentZ;
for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; TexCoordIndex++)
{
//Add this vertex instance tex coord
VertexInstanceUVs.Set(VertexInstanceID, TexCoordIndex, SoftVertex.UVs[TexCoordIndex]);
}
//Add this vertex instance color
VertexInstanceColors[VertexInstanceID] = bPropagateVertexColours ? FVector4(FLinearColor(SoftVertex.Color)) : FVector4(1.0f, 1.0f, 1.0f);
}
//Create a polygon from this triangle
TArray<FMeshDescription::FContourPoint> Contours;
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
int32 ContourPointIndex = Contours.AddDefaulted();
FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex];
//Find the matching edge ID
uint32 CornerIndices[2];
CornerIndices[0] = (CornerIndex + 0) % 3;
CornerIndices[1] = (CornerIndex + 1) % 3;
FVertexID EdgeVertexIDs[2];
EdgeVertexIDs[0] = VertexIndexes[CornerIndices[0]];
EdgeVertexIDs[1] = VertexIndexes[CornerIndices[1]];
FEdgeID MatchEdgeId = RawMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
if (MatchEdgeId == FEdgeID::Invalid)
{
MatchEdgeId = RawMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
EdgeHardnesses[MatchEdgeId] = false;
EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f;
}
ContourPoint.EdgeID = MatchEdgeId;
ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]];
}
// Insert a polygon into the mesh
const FPolygonID NewPolygonID = RawMesh.CreatePolygon(SectionPolygonGroupID, Contours);
//Triangulate the polygon
FMeshPolygon& Polygon = RawMesh.GetPolygon(NewPolygonID);
RawMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles);
}
}
}
}
void FMeshMergeHelpers::RetrieveMesh(const UStaticMesh* StaticMesh, int32 LODIndex, FMeshDescription& RawMesh)
{
const FStaticMeshSourceModel& StaticMeshModel = StaticMesh->SourceModels[LODIndex];
// Imported meshes will have a valid mesh description
const bool bImportedMesh = StaticMesh->IsMeshDescriptionValid(LODIndex);
// Check whether or not this mesh has been reduced in-engine
const bool bReducedMesh = StaticMesh->IsReductionActive(LODIndex);
// Trying to retrieve rawmesh from SourceStaticMeshModel was giving issues, which causes a mismatch
const bool bRenderDataMismatch = (LODIndex > 0) || StaticMeshModel.BuildSettings.bGenerateLightmapUVs;
if (bImportedMesh && !bReducedMesh && !bRenderDataMismatch)
{
RawMesh = *StaticMesh->GetMeshDescription(LODIndex);
}
else
{
ExportStaticMeshLOD(StaticMesh->RenderData->LODResources[LODIndex], RawMesh, StaticMesh->StaticMaterials);
}
// Make sure the raw mesh is not irreparably malformed.
if (RawMesh.VertexInstances().Num() <= 0)
{
// wrong
bool check = true;
}
// Use build settings from base mesh for LOD entries that was generated inside Editor.
const FMeshBuildSettings& BuildSettings = bImportedMesh ? StaticMeshModel.BuildSettings : StaticMesh->SourceModels[0].BuildSettings;
// Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
uint32 TangentOptions = FMeshDescriptionOperations::ETangentOptions::BlendOverlappingNormals;
if (BuildSettings.bRemoveDegenerates)
{
// If removing degenerate triangles, ignore them when computing tangents.
TangentOptions |= FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles;
}
FMeshDescriptionOperations::RecomputeNormalsAndTangentsIfNeeded(RawMesh, (FMeshDescriptionOperations::ETangentOptions)TangentOptions, BuildSettings.bUseMikkTSpace, (bImportedMesh && BuildSettings.bRecomputeNormals), (bImportedMesh && BuildSettings.bRecomputeTangents));
}
void FMeshMergeHelpers::ExportStaticMeshLOD(const FStaticMeshLODResources& StaticMeshLOD, FMeshDescription& OutRawMesh, const TArray<FStaticMaterial>& Materials)
{
const int32 NumWedges = StaticMeshLOD.IndexBuffer.GetNumIndices();
const int32 NumVertexPositions = StaticMeshLOD.VertexBuffers.PositionVertexBuffer.GetNumVertices();
const int32 NumFaces = NumWedges / 3;
OutRawMesh.Empty();
if (NumVertexPositions <= 0 || StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() <= 0)
{
return;
}
TVertexAttributesRef<FVector> VertexPositions = OutRawMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TEdgeAttributesRef<bool> EdgeHardnesses = OutRawMesh.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesRef<float> EdgeCreaseSharpnesses = OutRawMesh.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = OutRawMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
OutRawMesh.ReserveNewVertices(NumVertexPositions);
OutRawMesh.ReserveNewVertexInstances(NumWedges);
OutRawMesh.ReserveNewPolygons(NumFaces);
OutRawMesh.ReserveNewEdges(NumWedges);
const int32 NumTexCoords = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords();
VertexInstanceUVs.SetNumIndices(NumTexCoords);
for (int32 SectionIndex = 0; SectionIndex < StaticMeshLOD.Sections.Num(); ++SectionIndex)
{
const FStaticMeshSection& Section = StaticMeshLOD.Sections[SectionIndex];
FPolygonGroupID CurrentPolygonGroupID = OutRawMesh.CreatePolygonGroup();
check(CurrentPolygonGroupID.GetValue() == SectionIndex);
if (Materials.IsValidIndex(Section.MaterialIndex))
{
PolygonGroupImportedMaterialSlotNames[CurrentPolygonGroupID] = Materials[Section.MaterialIndex].ImportedMaterialSlotName;
}
else
{
PolygonGroupImportedMaterialSlotNames[CurrentPolygonGroupID] = FName(*(TEXT("MeshMergeMaterial_") + FString::FromInt(SectionIndex)));
}
}
//Create the vertex
for (int32 VertexIndex = 0; VertexIndex < NumVertexPositions; ++VertexIndex)
{
FVertexID VertexID = OutRawMesh.CreateVertex();
VertexPositions[VertexID] = StaticMeshLOD.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex);
}
//Create the vertex instances
for (int32 TriangleIndex = 0; TriangleIndex < NumFaces; ++TriangleIndex)
{
FPolygonGroupID CurrentPolygonGroupID = FPolygonGroupID::Invalid;
for (int32 SectionIndex = 0; SectionIndex < StaticMeshLOD.Sections.Num(); ++SectionIndex)
{
const FStaticMeshSection& Section = StaticMeshLOD.Sections[SectionIndex];
uint32 FirstTriangle = Section.FirstIndex / 3;
uint32 LastTriangle = FirstTriangle + Section.NumTriangles - 1;
if ((uint32)TriangleIndex >= FirstTriangle && (uint32)TriangleIndex <= LastTriangle)
{
CurrentPolygonGroupID = FPolygonGroupID(SectionIndex);
break;
}
}
check(CurrentPolygonGroupID != FPolygonGroupID::Invalid);
FVertexID VertexIDs[3];
FVertexInstanceID VertexInstanceIDs[3];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 WedgeIndex = StaticMeshLOD.IndexBuffer.GetIndex(TriangleIndex * 3 + Corner);
FVertexID VertexID(WedgeIndex);
FVertexInstanceID VertexInstanceID = OutRawMesh.CreateVertexInstance(VertexID);
VertexIDs[Corner] = VertexID;
VertexInstanceIDs[Corner] = VertexInstanceID;
//NTBs
FVector TangentX = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(WedgeIndex);
FVector TangentY = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(WedgeIndex);
FVector TangentZ = StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(WedgeIndex);
VertexInstanceTangents[VertexInstanceID] = TangentX;
VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(TangentX, TangentY, TangentZ);
VertexInstanceNormals[VertexInstanceID] = TangentZ;
// Vertex colors
if (StaticMeshLOD.VertexBuffers.ColorVertexBuffer.GetNumVertices() > 0)
{
VertexInstanceColors[VertexInstanceID] = FLinearColor(StaticMeshLOD.VertexBuffers.ColorVertexBuffer.VertexColor(WedgeIndex));
}
else
{
VertexInstanceColors[VertexInstanceID] = FLinearColor::White;
}
//Tex coord
for (int32 TexCoodIdx = 0; TexCoodIdx < NumTexCoords; ++TexCoodIdx)
{
VertexInstanceUVs.Set(VertexInstanceID, TexCoodIdx, StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(WedgeIndex, TexCoodIdx));
}
}
//Create a polygon from this triangle
TArray<FMeshDescription::FContourPoint> Contours;
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 ContourPointIndex = Contours.AddDefaulted();
FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex];
//Find the matching edge ID
uint32 CornerIndices[2];
CornerIndices[0] = (Corner + 0) % 3;
CornerIndices[1] = (Corner + 1) % 3;
FVertexID EdgeVertexIDs[2];
EdgeVertexIDs[0] = VertexIDs[CornerIndices[0]];
EdgeVertexIDs[1] = VertexIDs[CornerIndices[1]];
FEdgeID MatchEdgeId = OutRawMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
if (MatchEdgeId == FEdgeID::Invalid)
{
MatchEdgeId = OutRawMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
EdgeHardnesses[MatchEdgeId] = false;
EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f;
}
ContourPoint.EdgeID = MatchEdgeId;
ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]];
}
// Insert a polygon into the mesh
const FPolygonID NewPolygonID = OutRawMesh.CreatePolygon(CurrentPolygonGroupID, Contours);
//Triangulate the polygon
FMeshPolygon& Polygon = OutRawMesh.GetPolygon(NewPolygonID);
OutRawMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles);
}
}
bool FMeshMergeHelpers::CheckWrappingUVs(const TArray<FVector2D>& UVs)
{
bool bResult = false;
FVector2D Min(FLT_MAX, FLT_MAX);
FVector2D Max(-FLT_MAX, -FLT_MAX);
for (const FVector2D& Coordinate : UVs)
{
if ((FMath::IsNegativeFloat(Coordinate.X) || FMath::IsNegativeFloat(Coordinate.Y)) || (Coordinate.X > (1.0f + KINDA_SMALL_NUMBER) || Coordinate.Y > (1.0f + KINDA_SMALL_NUMBER)))
{
bResult = true;
break;
}
}
return bResult;
}
bool FMeshMergeHelpers::CheckWrappingUVs(const FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
bool bResult = false;
//Validate the channel, return false if there is an invalid channel index
if (UVChannelIndex < 0 || UVChannelIndex >= VertexInstanceUVs.GetNumIndices())
{
return bResult;
}
for (const FVertexInstanceID& VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
const FVector2D& Coordinate = VertexInstanceUVs.Get(VertexInstanceID, UVChannelIndex);
if ((FMath::IsNegativeFloat(Coordinate.X) || FMath::IsNegativeFloat(Coordinate.Y)) || (Coordinate.X > (1.0f + KINDA_SMALL_NUMBER) || Coordinate.Y > (1.0f + KINDA_SMALL_NUMBER)))
{
bResult = true;
break;
}
}
return bResult;
}
void FMeshMergeHelpers::CullTrianglesFromVolumesAndUnderLandscapes(const UWorld* World, const FBoxSphereBounds& Bounds, FMeshDescription& InOutRawMesh)
{
TArray<ALandscapeProxy*> Landscapes;
TArray<AMeshMergeCullingVolume*> CullVolumes;
FBox BoxBounds = Bounds.GetBox();
for (ULevel* Level : World->GetLevels())
{
for (AActor* Actor : Level->Actors)
{
ALandscape* Proxy = Cast<ALandscape>(Actor);
if (Proxy && Proxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
// Ignore Z axis for 2d bounds check
if (LandscapeBox.IntersectXY(BoxBounds))
{
Landscapes.Add(Proxy->GetLandscapeActor());
}
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(Bounds.Origin, Bounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
TVertexAttributesConstRef<FVector> VertexPositions = InOutRawMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TMap<FVertexID, bool> VertexVisible;
VertexVisible.Reserve(InOutRawMesh.Vertices().Num());
int32 Index = 0;
for(const FVertexID& VertexID : InOutRawMesh.Vertices().GetElementIDs())
{
const FVector& Position = VertexPositions[VertexID];
// Start with setting visibility to true on all vertices
VertexVisible.Add(VertexID, true);
// Check if this vertex is culled due to being underneath a landscape
if (Landscapes.Num() > 0)
{
bool bVertexWithinLandscapeBounds = false;
for (ALandscapeProxy* Proxy : Landscapes)
{
FVector Origin, Extent;
Proxy->GetActorBounds(false, Origin, Extent);
FBox LandscapeBox(Origin - Extent, Origin + Extent);
bVertexWithinLandscapeBounds |= LandscapeBox.IsInsideXY(Position);
}
if (bVertexWithinLandscapeBounds)
{
const FVector Start = Position;
FVector End = Position - (WORLD_MAX * FVector::UpVector);
FVector OutHit;
const bool IsAboveLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
End = Position + (WORLD_MAX * FVector::UpVector);
const bool IsUnderneathLandscape = IsLandscapeHit(Start, End, World, Landscapes, OutHit);
// Vertex is visible when above landscape (with actual landscape underneath) or if there is no landscape beneath or above the vertex (falls outside of landscape bounds)
VertexVisible[VertexID] = (IsAboveLandscape && !IsUnderneathLandscape);// || (!IsAboveLandscape && !IsUnderneathLandscape);
}
}
// Volume culling
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
const bool bVertexIsInsideVolume = Volume->EncompassesPoint(Position, 0.0f, nullptr);
if (bVertexIsInsideVolume)
{
// Inside a culling volume so invisible
VertexVisible[VertexID] = false;
}
}
Index++;
}
// We now know which vertices are below the landscape
TArray<FPolygonID> PolygonToRemove;
for(const FPolygonID& PolygonID : InOutRawMesh.Polygons().GetElementIDs())
{
bool AboveLandscape = false;
for (FMeshTriangle& Triangle : InOutRawMesh.GetPolygonTriangles(PolygonID))
{
for (int32 Corner = 0; Corner < 3; ++Corner)
{
AboveLandscape |= VertexVisible[InOutRawMesh.GetVertexInstanceVertex(Triangle.GetVertexInstanceID(Corner))];
}
}
if (!AboveLandscape)
{
PolygonToRemove.Add(PolygonID);
}
}
// Delete the polygons that are not visible
{
TArray<FEdgeID> OrphanedEdges;
TArray<FVertexInstanceID> OrphanedVertexInstances;
TArray<FPolygonGroupID> OrphanedPolygonGroups;
TArray<FVertexID> OrphanedVertices;
for (FPolygonID PolygonID : PolygonToRemove)
{
InOutRawMesh.DeletePolygon(PolygonID, &OrphanedEdges, &OrphanedVertexInstances, &OrphanedPolygonGroups);
}
//Do not remove the polygongroup since its indexed with the mesh material array
/*for (FPolygonGroupID PolygonGroupID : OrphanedPolygonGroups)
{
InOutRawMesh.DeletePolygonGroup(PolygonGroupID);
}*/
for (FVertexInstanceID VertexInstanceID : OrphanedVertexInstances)
{
InOutRawMesh.DeleteVertexInstance(VertexInstanceID, &OrphanedVertices);
}
for (FEdgeID EdgeID : OrphanedEdges)
{
InOutRawMesh.DeleteEdge(EdgeID, &OrphanedVertices);
}
for (FVertexID VertexID : OrphanedVertices)
{
InOutRawMesh.DeleteVertex(VertexID);
}
//Compact and Remap IDs so we have clean ID from 0 to n since we just erase some polygons
//The render build need to have compact ID
FElementIDRemappings OutRemappings;
InOutRawMesh.Compact(OutRemappings);
}
}
void FMeshMergeHelpers::PropagateSplineDeformationToRawMesh(const USplineMeshComponent* InSplineMeshComponent, FMeshDescription &OutRawMesh)
{
TVertexAttributesRef<FVector> VertexPositions = OutRawMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
// Apply spline deformation for each vertex's tangents
int32 WedgeIndex = 0;
for (const FPolygonID& PolygonID : OutRawMesh.Polygons().GetElementIDs())
{
for (FMeshTriangle& Triangle : OutRawMesh.GetPolygonTriangles(PolygonID))
{
for (int32 Corner = 0; Corner < 3; ++Corner, ++WedgeIndex)
{
const FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(Corner);
const FVertexID VertexID = OutRawMesh.GetVertexInstanceVertex(VertexInstanceID);
const float& AxisValue = USplineMeshComponent::GetAxisValue(VertexPositions[VertexID], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
FVector TangentY = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
VertexInstanceTangents[VertexInstanceID] = SliceTransform.TransformVector(VertexInstanceTangents[VertexInstanceID]);
TangentY = SliceTransform.TransformVector(TangentY);
VertexInstanceNormals[VertexInstanceID] = SliceTransform.TransformVector(VertexInstanceNormals[VertexInstanceID]);
VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(VertexInstanceTangents[VertexInstanceID], TangentY, VertexInstanceNormals[VertexInstanceID]);
}
}
}
// Apply spline deformation for each vertex position
for (const FVertexID& VertexID : OutRawMesh.Vertices().GetElementIDs())
{
float& AxisValue = USplineMeshComponent::GetAxisValue(VertexPositions[VertexID], InSplineMeshComponent->ForwardAxis);
FTransform SliceTransform = InSplineMeshComponent->CalcSliceTransform(AxisValue);
AxisValue = 0.0f;
VertexPositions[VertexID] = SliceTransform.TransformPosition(VertexPositions[VertexID]);
}
}
void FMeshMergeHelpers::PropagateSplineDeformationToPhysicsGeometry(USplineMeshComponent* SplineMeshComponent, FKAggregateGeom& InOutPhysicsGeometry)
{
const FVector Mask = USplineMeshComponent::GetAxisMask(SplineMeshComponent->GetForwardAxis());
for (FKConvexElem& Elem : InOutPhysicsGeometry.ConvexElems)
{
for (FVector& Position : Elem.VertexData)
{
const float& AxisValue = USplineMeshComponent::GetAxisValue(Position, SplineMeshComponent->ForwardAxis);
FTransform SliceTransform = SplineMeshComponent->CalcSliceTransform(AxisValue);
Position = SliceTransform.TransformPosition(Position * Mask);
}
Elem.UpdateElemBox();
}
for (FKSphereElem& Elem : InOutPhysicsGeometry.SphereElems)
{
const FVector WorldSpaceCenter = Elem.GetTransform().TransformPosition(Elem.Center);
Elem.Center = SplineMeshComponent->CalcSliceTransform(USplineMeshComponent::GetAxisValue(WorldSpaceCenter, SplineMeshComponent->ForwardAxis)).TransformPosition(Elem.Center * Mask);
}
for (FKSphylElem& Elem : InOutPhysicsGeometry.SphylElems)
{
const FVector WorldSpaceCenter = Elem.GetTransform().TransformPosition(Elem.Center);
Elem.Center = SplineMeshComponent->CalcSliceTransform(USplineMeshComponent::GetAxisValue(WorldSpaceCenter, SplineMeshComponent->ForwardAxis)).TransformPosition(Elem.Center * Mask);
}
}
void FMeshMergeHelpers::TransformRawMeshVertexData(const FTransform& InTransform, FMeshDescription &OutRawMesh)
{
TVertexAttributesRef<FVector> VertexPositions = OutRawMesh.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TEdgeAttributesRef<bool> EdgeHardnesses = OutRawMesh.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesRef<float> EdgeCreaseSharpnesses = OutRawMesh.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = OutRawMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = OutRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
for(const FVertexID& VertexID : OutRawMesh.Vertices().GetElementIDs())
{
VertexPositions[VertexID] = InTransform.TransformPosition(VertexPositions[VertexID]);
}
auto TransformNormal = [&](FVector& Normal)
{
FMatrix Matrix = InTransform.ToMatrixWithScale();
const float DetM = Matrix.Determinant();
FMatrix AdjointT = Matrix.TransposeAdjoint();
AdjointT.RemoveScaling();
Normal = AdjointT.TransformVector(Normal);
if (DetM < 0.f)
{
Normal *= -1.0f;
}
};
for(const FVertexInstanceID& VertexInstanceID : OutRawMesh.VertexInstances().GetElementIDs())
{
FVector TangentY = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
TransformNormal(VertexInstanceTangents[VertexInstanceID]);
TransformNormal(TangentY);
TransformNormal(VertexInstanceNormals[VertexInstanceID]);
VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(VertexInstanceTangents[VertexInstanceID], TangentY, VertexInstanceNormals[VertexInstanceID]);
}
const bool bIsMirrored = InTransform.GetDeterminant() < 0.f;
if (bIsMirrored)
{
//Reverse the vertexinstance
OutRawMesh.ReverseAllPolygonFacing();
}
}
void FMeshMergeHelpers::RetrieveCullingLandscapeAndVolumes(UWorld* InWorld, const FBoxSphereBounds& EstimatedMeshProxyBounds, const TEnumAsByte<ELandscapeCullingPrecision::Type> PrecisionType, TArray<FMeshDescription*>& CullingRawMeshes)
{
// Extract landscape proxies and cull volumes from the world
TArray<ALandscapeProxy*> LandscapeActors;
TArray<AMeshMergeCullingVolume*> CullVolumes;
uint32 MaxLandscapeExportLOD = 0;
if (InWorld->IsValidLowLevel())
{
for (FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator)
{
for (AActor* Actor : (*Iterator)->Actors)
{
if (Actor)
{
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(Actor);
if (LandscapeProxy && LandscapeProxy->bUseLandscapeForCullingInvisibleHLODVertices)
{
// Retrieve highest landscape LOD level possible
MaxLandscapeExportLOD = FMath::Max(MaxLandscapeExportLOD, FMath::CeilLogTwo(LandscapeProxy->SubsectionSizeQuads + 1) - 1);
LandscapeActors.Add(LandscapeProxy);
}
// Check for culling volumes
AMeshMergeCullingVolume* Volume = Cast<AMeshMergeCullingVolume>(Actor);
if (Volume)
{
// If the mesh's bounds intersect with the volume there is a possibility of culling
const bool bIntersecting = Volume->EncompassesPoint(EstimatedMeshProxyBounds.Origin, EstimatedMeshProxyBounds.SphereRadius, nullptr);
if (bIntersecting)
{
CullVolumes.Add(Volume);
}
}
}
}
}
}
// Setting determines the precision at which we should export the landscape for culling (highest, half or lowest)
const uint32 LandscapeExportLOD = ((float)MaxLandscapeExportLOD * (0.5f * (float)PrecisionType));
for (ALandscapeProxy* Landscape : LandscapeActors)
{
// Export the landscape to raw mesh format
FMeshDescription MeshDescription;
UStaticMesh::RegisterMeshAttributes(MeshDescription);
FBoxSphereBounds LandscapeBounds = EstimatedMeshProxyBounds;
Landscape->ExportToRawMesh(LandscapeExportLOD, MeshDescription, LandscapeBounds);
if (MeshDescription.Vertices().Num())
{
CullingRawMeshes.Add(&MeshDescription);
}
}
// Also add volume mesh data as culling meshes
for (AMeshMergeCullingVolume* Volume : CullVolumes)
{
// Export the landscape to raw mesh format
FMeshDescription* VolumeMesh = new FMeshDescription();
UStaticMesh::RegisterMeshAttributes(*VolumeMesh);
TArray<FStaticMaterial> VolumeMaterials;
GetBrushMesh(Volume, Volume->Brush, *VolumeMesh, VolumeMaterials);
// Offset vertices to correct world position;
FVector VolumeLocation = Volume->GetActorLocation();
TVertexAttributesRef<FVector> VertexPositions = VolumeMesh->VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
for(const FVertexID& VertexID : VolumeMesh->Vertices().GetElementIDs())
{
VertexPositions[VertexID] += VolumeLocation;
}
CullingRawMeshes.Add(VolumeMesh);
}
}
void FMeshMergeHelpers::TransformPhysicsGeometry(const FTransform& InTransform, const bool bBakeConvexTransform, struct FKAggregateGeom& AggGeom)
{
FTransform NoScaleInTransform = InTransform;
NoScaleInTransform.SetScale3D(FVector(1, 1, 1));
// Pre-scale all non-convex geometry
const FVector Scale3D = InTransform.GetScale3D();
if (!Scale3D.Equals(FVector(1.f)))
{
const float MinPrimSize = KINDA_SMALL_NUMBER;
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
Elem = Elem.GetFinalScaled(Scale3D, FTransform::Identity);
}
}
// Multiply out merge transform (excluding scale) with original transforms for non-convex geometry
for (FKSphereElem& Elem : AggGeom.SphereElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKBoxElem& Elem : AggGeom.BoxElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKSphylElem& Elem : AggGeom.SphylElems)
{
FTransform ElemTM = Elem.GetTransform();
Elem.SetTransform(ElemTM*NoScaleInTransform);
}
for (FKConvexElem& Elem : AggGeom.ConvexElems)
{
FTransform ElemTM = Elem.GetTransform();
if (bBakeConvexTransform)
{
for (FVector& Position : Elem.VertexData)
{
Position = ElemTM.TransformPosition(Position);
}
Elem.SetTransform(InTransform);
}
else
{
Elem.SetTransform(ElemTM*InTransform);
}
}
}
void FMeshMergeHelpers::ExtractPhysicsGeometry(UBodySetup* InBodySetup, const FTransform& ComponentToWorld, const bool bBakeConvexTransform, struct FKAggregateGeom& OutAggGeom)
{
if (InBodySetup == nullptr)
{
return;
}
OutAggGeom = InBodySetup->AggGeom;
// Convert boxes to convex, so they can be sheared
for (int32 BoxIdx = 0; BoxIdx < OutAggGeom.BoxElems.Num(); BoxIdx++)
{
FKConvexElem* NewConvexColl = new(OutAggGeom.ConvexElems) FKConvexElem();
NewConvexColl->ConvexFromBoxElem(OutAggGeom.BoxElems[BoxIdx]);
}
OutAggGeom.BoxElems.Empty();
// we are not owner of this stuff
OutAggGeom.RenderInfo = nullptr;
for (FKConvexElem& Elem : OutAggGeom.ConvexElems)
{
Elem.SetConvexMesh(nullptr);
Elem.SetMirroredConvexMesh(nullptr);
}
// Transform geometry to world space
TransformPhysicsGeometry(ComponentToWorld, bBakeConvexTransform, OutAggGeom);
}
FVector2D FMeshMergeHelpers::GetValidUV(const FVector2D& UV)
{
FVector2D NewUV = UV;
// first make sure they're positive
if (UV.X < 0.0f)
{
NewUV.X = UV.X + FMath::CeilToInt(FMath::Abs(UV.X));
}
if (UV.Y < 0.0f)
{
NewUV.Y = UV.Y + FMath::CeilToInt(FMath::Abs(UV.Y));
}
// now make sure they're within [0, 1]
if (UV.X > 1.0f)
{
NewUV.X = FMath::Fmod(NewUV.X, 1.0f);
}
if (UV.Y > 1.0f)
{
NewUV.Y = FMath::Fmod(NewUV.Y, 1.0f);
}
return NewUV;
}
void FMeshMergeHelpers::CalculateTextureCoordinateBoundsForRawMesh(const FMeshDescription& InRawMesh, TArray<FBox2D>& OutBounds)
{
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = InRawMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
OutBounds.Empty();
for (const FPolygonID& PolygonID : InRawMesh.Polygons().GetElementIDs())
{
int32 MaterialIndex = InRawMesh.GetPolygonPolygonGroup(PolygonID).GetValue();
if (OutBounds.Num() <= MaterialIndex)
OutBounds.SetNumZeroed(MaterialIndex + 1);
{
TArray<FVertexInstanceID> PolygonVertexInstances = InRawMesh.GetPolygonPerimeterVertexInstances(PolygonID);
for (const FVertexInstanceID& VertexInstanceID : PolygonVertexInstances)
{
for (int32 UVIndex = 0; UVIndex < VertexInstanceUVs.GetNumIndices(); ++UVIndex)
{
OutBounds[MaterialIndex] += VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
}
}
}
}
}
bool FMeshMergeHelpers::PropagatePaintedColorsToRawMesh(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, FMeshDescription& RawMesh)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh->SourceModels.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData.IsValidIndex(LODIndex) &&
StaticMeshComponent->LODData[LODIndex].OverrideVertexColors != nullptr)
{
FColorVertexBuffer& ColorVertexBuffer = *StaticMeshComponent->LODData[LODIndex].OverrideVertexColors;
FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[LODIndex];
if (ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices())
{
const int32 NumWedges = RawMesh.VertexInstances().Num();
const int32 NumRenderWedges = RenderModel.IndexBuffer.GetNumIndices();
const bool bUseRenderWedges = NumWedges == NumRenderWedges;
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
if (bUseRenderWedges)
{
//Create a map index
TMap<int32, FVertexInstanceID> IndexToVertexInstanceID;
IndexToVertexInstanceID.Reserve(NumWedges);
int32 CurrentWedgeIndex = 0;
for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs())
{
const TArray<FMeshTriangle>& Triangles = RawMesh.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle& Triangle : Triangles)
{
for (int32 Corner = 0; Corner < 3; ++Corner, ++CurrentWedgeIndex)
{
IndexToVertexInstanceID.Add(CurrentWedgeIndex, Triangle.GetVertexInstanceID(Corner));
}
}
}
const FIndexArrayView ArrayView = RenderModel.IndexBuffer.GetArrayView();
for (int32 WedgeIndex = 0; WedgeIndex < NumRenderWedges; WedgeIndex++)
{
const int32 Index = ArrayView[WedgeIndex];
FColor WedgeColor = FColor::White;
if (Index != INDEX_NONE)
{
WedgeColor = ColorVertexBuffer.VertexColor(Index);
}
VertexInstanceColors[IndexToVertexInstanceID[WedgeIndex]] = FLinearColor(WedgeColor);
}
return true;
}
// No wedge map (this can happen when we poly reduce the LOD for example)
// Use index buffer directly. Not sure this will happen with FMeshDescription
else
{
if (RawMesh.Vertices().Num() == ColorVertexBuffer.GetNumVertices())
{
//Create a map index
TMap<FVertexID, int32> VertexIDToVertexIndex;
VertexIDToVertexIndex.Reserve(RawMesh.Vertices().Num());
int32 CurrentVertexIndex = 0;
for (const FVertexID& VertexID : RawMesh.Vertices().GetElementIDs())
{
VertexIDToVertexIndex.Add(VertexID, CurrentVertexIndex++);
}
for (const FVertexID& VertexID : RawMesh.Vertices().GetElementIDs())
{
FColor WedgeColor = FColor::White;
uint32 VertIndex = VertexIDToVertexIndex[VertexID];
if (VertIndex < ColorVertexBuffer.GetNumVertices())
{
WedgeColor = ColorVertexBuffer.VertexColor(VertIndex);
}
const TArray<FVertexInstanceID>& VertexInstances = RawMesh.GetVertexVertexInstances(VertexID);
for (const FVertexInstanceID& VertexInstanceID : VertexInstances)
{
VertexInstanceColors[VertexInstanceID] = FLinearColor(WedgeColor);
}
}
return true;
}
}
}
}
return false;
}
bool FMeshMergeHelpers::IsLandscapeHit(const FVector& RayOrigin, const FVector& RayEndPoint, const UWorld* World, const TArray<ALandscapeProxy*>& LandscapeProxies, FVector& OutHitLocation)
{
TArray<FHitResult> Results;
// Each landscape component has 2 collision shapes, 1 of them is specific to landscape editor
// Trace only ECC_Visibility channel, so we do hit only Editor specific shape
World->LineTraceMultiByObjectType(Results, RayOrigin, RayEndPoint, FCollisionObjectQueryParams(ECollisionChannel::ECC_WorldStatic), FCollisionQueryParams(SCENE_QUERY_STAT(LandscapeTrace), true));
bool bHitLandscape = false;
for (const FHitResult& HitResult : Results)
{
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Cast<ULandscapeHeightfieldCollisionComponent>(HitResult.Component.Get());
if (CollisionComponent)
{
ALandscapeProxy* HitLandscape = CollisionComponent->GetLandscapeProxy();
if (HitLandscape && LandscapeProxies.Contains(HitLandscape))
{
// Could write a correct clipping algorithm, that clips the triangle to hit location
OutHitLocation = HitLandscape->LandscapeActorToWorld().InverseTransformPosition(HitResult.Location);
// Above landscape so visible
bHitLandscape = true;
}
}
}
return bHitLandscape;
}
void FMeshMergeHelpers::AppendRawMesh(FMeshDescription& InTarget, const FMeshDescription& InSource)
{
TVertexAttributesConstRef<FVector> SourceVertexPositions = InSource.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TEdgeAttributesConstRef<bool> SourceEdgeHardnesses = InSource.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesConstRef<float> SourceEdgeCreaseSharpnesses = InSource.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesConstRef<FName> SourcePolygonGroupImportedMaterialSlotNames = InSource.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
TVertexInstanceAttributesConstRef<FVector> SourceVertexInstanceNormals = InSource.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesConstRef<FVector> SourceVertexInstanceTangents = InSource.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesConstRef<float> SourceVertexInstanceBinormalSigns = InSource.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesConstRef<FVector4> SourceVertexInstanceColors = InSource.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesConstRef<FVector2D> SourceVertexInstanceUVs = InSource.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TVertexAttributesRef<FVector> TargetVertexPositions = InTarget.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TEdgeAttributesRef<bool> TargetEdgeHardnesses = InTarget.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesRef<float> TargetEdgeCreaseSharpnesses = InTarget.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesRef<FName> TargetPolygonGroupImportedMaterialSlotNames = InTarget.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
TVertexInstanceAttributesRef<FVector> TargetVertexInstanceNormals = InTarget.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> TargetVertexInstanceTangents = InTarget.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> TargetVertexInstanceBinormalSigns = InTarget.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> TargetVertexInstanceColors = InTarget.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> TargetVertexInstanceUVs = InTarget.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
InTarget.ReserveNewVertices(InSource.Vertices().Num());
InTarget.ReserveNewVertexInstances(InSource.VertexInstances().Num());
InTarget.ReserveNewEdges(InSource.Edges().Num());
InTarget.ReserveNewPolygons(InSource.Vertices().Num());
//Append PolygonGroup
for (const FPolygonGroupID& SourcePolygonGroupID : InSource.PolygonGroups().GetElementIDs())
{
if (!InTarget.IsPolygonGroupValid(SourcePolygonGroupID))
{
InTarget.CreatePolygonGroupWithID(SourcePolygonGroupID);
TargetPolygonGroupImportedMaterialSlotNames[SourcePolygonGroupID] = SourcePolygonGroupImportedMaterialSlotNames[SourcePolygonGroupID];
}
}
//Append the Vertexs
TMap<FVertexID, FVertexID> SourceToTargetVertexID;
SourceToTargetVertexID.Reserve(InSource.Vertices().Num());
for (const FVertexID& SourceVertexID : InSource.Vertices().GetElementIDs())
{
const FVertexID TargetVertexID = InTarget.CreateVertex();
SourceToTargetVertexID.Add(SourceVertexID, TargetVertexID);
}
//Append VertexInstances
if (SourceVertexInstanceUVs.GetNumIndices() > TargetVertexInstanceUVs.GetNumIndices())
{
TargetVertexInstanceUVs.SetNumIndices(SourceVertexInstanceUVs.GetNumIndices());
}
TMap<FVertexInstanceID, FVertexInstanceID> SourceToTargetVertexInstanceID;
SourceToTargetVertexInstanceID.Reserve(InSource.VertexInstances().Num());
for (const FVertexInstanceID& SourceVertexInstanceID : InSource.VertexInstances().GetElementIDs())
{
const FVertexID SourceVertexID = InSource.GetVertexInstanceVertex(SourceVertexInstanceID);
const FVertexInstanceID TargetVertexInstanceID = InTarget.CreateVertexInstance(SourceToTargetVertexID[SourceVertexID]);
TargetVertexInstanceTangents[TargetVertexInstanceID] = SourceVertexInstanceTangents[SourceVertexInstanceID];
TargetVertexInstanceBinormalSigns[TargetVertexInstanceID] = SourceVertexInstanceBinormalSigns[SourceVertexInstanceID];
TargetVertexInstanceNormals[TargetVertexInstanceID] = SourceVertexInstanceNormals[SourceVertexInstanceID];
TargetVertexInstanceColors[TargetVertexInstanceID] = SourceVertexInstanceColors[SourceVertexInstanceID];
for (int32 UVIndex = 0; UVIndex < TargetVertexInstanceUVs.GetNumIndices(); ++UVIndex)
{
FVector2D SourceUV = SourceVertexInstanceUVs.GetNumIndices() > UVIndex ? SourceVertexInstanceUVs.Get(SourceVertexInstanceID, UVIndex) : FVector2D(0.0f, 0.0f);
TargetVertexInstanceUVs.Set(TargetVertexInstanceID, UVIndex, SourceUV);
}
SourceToTargetVertexInstanceID.Add(SourceVertexInstanceID, TargetVertexInstanceID);
}
//Append Edges
TMap<FEdgeID, FEdgeID> SourceToTargetEdgeID;
SourceToTargetEdgeID.Reserve(InSource.Edges().Num());
for (const FEdgeID& SourceEdgeID : InSource.Edges().GetElementIDs())
{
const FMeshEdge& SourceEdge = InSource.GetEdge(SourceEdgeID);
const FEdgeID TargetEdgeID = InTarget.CreateEdge(SourceToTargetVertexID[SourceEdge.VertexIDs[0]], SourceToTargetVertexID[SourceEdge.VertexIDs[1]]);
TargetEdgeHardnesses[TargetEdgeID] = SourceEdgeHardnesses[SourceEdgeID];
TargetEdgeCreaseSharpnesses[TargetEdgeID] = SourceEdgeCreaseSharpnesses[SourceEdgeID];
SourceToTargetEdgeID.Add(SourceEdgeID, TargetEdgeID);
}
auto CreateContour = [&InSource, &SourceToTargetVertexInstanceID, &SourceToTargetEdgeID](const TArray<FVertexInstanceID>& SourceVertexInstanceIDs, TArray<FMeshDescription::FContourPoint>& Contours)
{
const int32 ContourCount = SourceVertexInstanceIDs.Num();
for (int32 ContourIndex = 0; ContourIndex < ContourCount; ++ContourIndex)
{
FVertexInstanceID SourceVertexInstanceID = SourceVertexInstanceIDs[ContourIndex];
int32 ContourPlusOne = (ContourIndex + 1) % ContourCount;
FVertexInstanceID SourceVertexInstanceID_Next = SourceVertexInstanceIDs[ContourPlusOne];
int32 ContourPointIndex = Contours.AddDefaulted();
FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex];
const FEdgeID SourceEdgeID = InSource.GetVertexPairEdge(InSource.GetVertexInstanceVertex(SourceVertexInstanceID), InSource.GetVertexInstanceVertex(SourceVertexInstanceID_Next));
check(SourceEdgeID != FEdgeID::Invalid);
ContourPoint.VertexInstanceID = SourceToTargetVertexInstanceID[SourceVertexInstanceID];
ContourPoint.EdgeID = SourceToTargetEdgeID[SourceEdgeID];
}
};
//Append Polygons
for (const FPolygonID& SourcePolygonID : InSource.Polygons().GetElementIDs())
{
const FMeshPolygon& SourcePolygon = InSource.GetPolygon(SourcePolygonID);
const TArray<FVertexInstanceID>& SourceVertexInstanceIDs = InSource.GetPolygonPerimeterVertexInstances(SourcePolygonID);
TArray<FMeshDescription::FContourPoint> Contours;
CreateContour(SourceVertexInstanceIDs, Contours);
int32 HoleNumber = InSource.GetNumPolygonHoles(SourcePolygonID);
TArray<TArray<FMeshDescription::FContourPoint>> AllHoles;
AllHoles.AddDefaulted(HoleNumber);
for (int32 HoleIndex = 0; HoleIndex < HoleNumber; ++HoleIndex)
{
const FMeshPolygonContour& HoleContour = SourcePolygon.HoleContours[HoleIndex];
TArray<FMeshDescription::FContourPoint>& Holes = AllHoles[HoleIndex];
CreateContour(HoleContour.VertexInstanceIDs, Holes);
}
// Insert a polygon into the mesh
const FPolygonID TargetPolygonID = InTarget.CreatePolygon(SourcePolygon.PolygonGroupID, Contours, AllHoles);
//Triangulate the polygon
FMeshPolygon& Polygon = InTarget.GetPolygon(TargetPolygonID);
InTarget.ComputePolygonTriangulation(TargetPolygonID, Polygon.Triangles);
}
}
void FMeshMergeHelpers::MergeImpostersToRawMesh(TArray<const UStaticMeshComponent*> ImposterComponents, FMeshDescription& InRawMesh, const FVector& InPivot, int32 InBaseMaterialIndex, TArray<UMaterialInterface*>& OutImposterMaterials)
{
// TODO decide whether we want this to be user specified or derived from the RawMesh
/*const int32 UVOneIndex = [RawMesh, Data]() -> int32
{
int32 ChannelIndex = 0;
for (; ChannelIndex < MAX_MESH_TEXTURE_COORDS; ++ChannelIndex)
{
if (RawMesh.WedgeTexCoords[ChannelIndex].Num() == 0)
{
break;
}
}
int32 MaxUVChannel = ChannelIndex;
for (const UStaticMeshComponent* Component : ImposterComponents)
{
MaxUVChannel = FMath::Max(MaxUVChannel, Component->GetStaticMesh()->RenderData->LODResources[Component->GetStaticMesh()->GetNumLODs() - 1].GetNumTexCoords());
}
return MaxUVChannel;
}();*/
const int32 UVOneIndex = 2; // if this is changed back to being dynamic, renable the if statement below
// Ensure there are enough UV channels available to store the imposter data
//if (UVOneIndex != INDEX_NONE && UVOneIndex < (MAX_MESH_TEXTURE_COORDS - 2))
{
for (const UStaticMeshComponent* Component : ImposterComponents)
{
// Retrieve imposter LOD mesh and material
const int32 LODIndex = Component->GetStaticMesh()->GetNumLODs() - 1;
// Retrieve mesh data in FMeshDescription form
FMeshDescription ImposterMesh;
FMeshMergeHelpers::RetrieveMesh(Component, LODIndex, ImposterMesh, false);
// Retrieve the sections, we're expect 1 for imposter meshes
TArray<FSectionInfo> Sections;
FMeshMergeHelpers::ExtractSections(Component, LODIndex, Sections);
for (FSectionInfo& Info : Sections)
{
OutImposterMaterials.AddUnique(Info.Material);
}
// Imposter magic, we're storing the actor world position and X scale spread across two UV channels
const int32 UVTwoIndex = UVOneIndex + 1;
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = ImposterMesh.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
VertexInstanceUVs.SetNumIndices(UVTwoIndex + 1);
const int32 NumIndices = ImposterMesh.VertexInstances().Num();
const FTransform& ActorToWorld = Component->GetOwner()->GetActorTransform();
const FVector ActorPosition = ActorToWorld.TransformPosition(FVector::ZeroVector) - InPivot;
for(const FVertexInstanceID& VertexInstanceID : ImposterMesh.VertexInstances().GetElementIDs())
{
FVector2D UVOne;
FVector2D UVTwo;
UVOne.X = ActorPosition.X;
UVOne.Y = ActorPosition.Y;
VertexInstanceUVs.Set(VertexInstanceID, UVOneIndex, UVOne);
UVTwo.X = ActorPosition.Z;
UVTwo.Y = ActorToWorld.GetScale3D().X;
VertexInstanceUVs.Set(VertexInstanceID, UVTwoIndex, UVTwo);
}
FMeshMergeHelpers::AppendRawMesh(InRawMesh, ImposterMesh);
}
}
}