You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
635 lines
22 KiB
C++
635 lines
22 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ConversionUtils/SceneComponentToDynamicMesh.h"
|
|
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Engine/SkinnedAssetCommon.h"
|
|
#include "UObject/Package.h"
|
|
|
|
#include "Components/SceneComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/SkinnedMeshComponent.h"
|
|
#include "Components/BrushComponent.h"
|
|
#include "Components/DynamicMeshComponent.h"
|
|
#include "Components/SplineMeshComponent.h"
|
|
#include "StaticMeshComponentLODInfo.h"
|
|
#include "ConversionUtils/VolumeToDynamicMesh.h"
|
|
#include "ConversionUtils/SkinnedMeshToDynamicMesh.h"
|
|
#include "ConversionUtils/SplineComponentDeformDynamicMesh.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
#include "DynamicMesh/MeshNormals.h"
|
|
#include "DynamicMesh/MeshTransforms.h"
|
|
#include "DynamicMeshEditor.h"
|
|
#include "GeometryCollection/GeometryCollectionComponent.h"
|
|
#include "GeometryCollection/GeometryCollectionObject.h"
|
|
#include "MeshDescription.h"
|
|
#include "MeshDescriptionToDynamicMesh.h"
|
|
#include "Physics/ComponentCollisionUtil.h"
|
|
#include "PlanarCut.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshLODResourcesToDynamicMesh.h"
|
|
#include "StaticMeshOperations.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "ModelingComponents_SceneComponentToDynamicMesh"
|
|
|
|
namespace UE
|
|
{
|
|
namespace Conversion
|
|
{
|
|
|
|
bool CanConvertSceneComponentToDynamicMesh(USceneComponent* Component)
|
|
{
|
|
if (!Component)
|
|
{
|
|
return false;
|
|
}
|
|
else if (const USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(Component))
|
|
{
|
|
#if WITH_EDITOR
|
|
const USkinnedAsset* SkinnedAsset = (!SkinnedMeshComponent->IsUnreachable() && SkinnedMeshComponent->IsValidLowLevel()) ? SkinnedMeshComponent->GetSkinnedAsset() : nullptr;
|
|
return SkinnedAsset && !SkinnedAsset->GetOutermost()->bIsCookedForEditor;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
else if (Cast<USplineMeshComponent>(Component))
|
|
{
|
|
return true;
|
|
}
|
|
else if (const UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
|
|
{
|
|
#if WITH_EDITOR
|
|
const UStaticMesh* StaticMesh = (!StaticMeshComponent->IsUnreachable() && StaticMeshComponent->IsValidLowLevel()) ? StaticMeshComponent->GetStaticMesh() : nullptr;
|
|
return StaticMesh && !StaticMesh->GetOutermost()->bIsCookedForEditor;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
else if (Cast<UDynamicMeshComponent>(Component))
|
|
{
|
|
return true;
|
|
}
|
|
else if (Cast<UBrushComponent>(Component))
|
|
{
|
|
return true;
|
|
}
|
|
else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast<UGeometryCollectionComponent>(Component))
|
|
{
|
|
#if WITH_EDITOR
|
|
const UGeometryCollection* GeometryCollectionAsset = (!GeometryCollectionComponent->IsUnreachable() && GeometryCollectionComponent->IsValidLowLevel()) ? GeometryCollectionComponent->GetRestCollection() : nullptr;
|
|
return GeometryCollectionAsset && !GeometryCollectionAsset->GetOutermost()->bIsCookedForEditor;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Conversion helpers
|
|
namespace Private::ConversionHelper
|
|
{
|
|
// Static mesh conversion functions (from geometry script MeshAssetFunctions.cpp)
|
|
// TODO: these static mesh conversion helpers should be pulled out to their own StaticMeshToDynamicMesh converter method
|
|
|
|
struct FStaticMeshConversionOptions
|
|
{
|
|
// Whether to apply Build Settings during the mesh copy.
|
|
bool bApplyBuildSettings = true;
|
|
|
|
// Whether to request tangents on the copied mesh. If tangents are not requested, tangent-related build settings will also be ignored.
|
|
bool bRequestTangents = true;
|
|
|
|
// Whether to ignore the 'remove degenerates' option from Build Settings. Note: Only applies if 'Apply Build Settings' is enabled.
|
|
bool bIgnoreRemoveDegenerates = true;
|
|
|
|
// Whether to scale the copied mesh by the Build Setting's 'Build Scale'. Note: This is considered separately from the 'Apply Build Settings' option.
|
|
bool bUseBuildScale = true;
|
|
|
|
// Whether to request the vertex colors of the component instancing the static mesh, rather than the static mesh asset
|
|
bool bRequestInstanceVertexColors = true;
|
|
};
|
|
|
|
static bool CopyMeshFromStaticMesh_SourceData(
|
|
UStaticMesh* FromStaticMeshAsset,
|
|
FStaticMeshConversionOptions AssetOptions,
|
|
EMeshLODType LODType,
|
|
int32 LODIndex,
|
|
FDynamicMesh3& OutMesh,
|
|
FText& OutErrorMessage
|
|
)
|
|
{
|
|
using namespace ::UE::Geometry;
|
|
|
|
bool bSuccess = false;
|
|
OutMesh.Clear();
|
|
|
|
if (!FromStaticMeshAsset)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMeshSource_NullMesh", "Static Mesh is null");
|
|
return false;
|
|
}
|
|
|
|
if (LODType != EMeshLODType::MaxAvailable && LODType != EMeshLODType::SourceModel && LODType != EMeshLODType::HiResSourceModel)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_LODNotAvailable", "Requested LOD Type is not available");
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (LODType == EMeshLODType::HiResSourceModel && FromStaticMeshAsset->IsHiResMeshDescriptionValid() == false)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_HiResLODNotAvailable", "HiResSourceModel LOD Type is not available");
|
|
return false;
|
|
}
|
|
|
|
const FMeshDescription* SourceMesh = nullptr;
|
|
const FMeshBuildSettings* BuildSettings = nullptr;
|
|
|
|
if ((LODType == EMeshLODType::HiResSourceModel) ||
|
|
(LODType == EMeshLODType::MaxAvailable && FromStaticMeshAsset->IsHiResMeshDescriptionValid()))
|
|
{
|
|
SourceMesh = FromStaticMeshAsset->GetHiResMeshDescription();
|
|
const FStaticMeshSourceModel& SourceModel = FromStaticMeshAsset->GetHiResSourceModel();
|
|
BuildSettings = &SourceModel.BuildSettings;
|
|
}
|
|
else
|
|
{
|
|
int32 UseLODIndex = FMath::Clamp(LODIndex, 0, FromStaticMeshAsset->GetNumSourceModels() - 1);
|
|
SourceMesh = FromStaticMeshAsset->GetMeshDescription(UseLODIndex);
|
|
const FStaticMeshSourceModel& SourceModel = FromStaticMeshAsset->GetSourceModel(UseLODIndex);
|
|
BuildSettings = &SourceModel.BuildSettings;
|
|
}
|
|
|
|
if (SourceMesh == nullptr)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_SourceLODIsNull", "Requested SourceModel LOD is null, only RenderData Mesh is available");
|
|
return false;
|
|
}
|
|
|
|
bool bHasDirtyBuildSettings = BuildSettings->bRecomputeNormals
|
|
|| (BuildSettings->bRecomputeTangents && AssetOptions.bRequestTangents);
|
|
bool bNeedsBuildScale = AssetOptions.bUseBuildScale && BuildSettings && !BuildSettings->BuildScale3D.Equals(FVector::OneVector);
|
|
bool bNeedsOtherBuildSettings = AssetOptions.bApplyBuildSettings && bHasDirtyBuildSettings;
|
|
|
|
FMeshDescription LocalSourceMeshCopy;
|
|
if (bNeedsBuildScale || bNeedsOtherBuildSettings)
|
|
{
|
|
LocalSourceMeshCopy = *SourceMesh;
|
|
|
|
FStaticMeshAttributes Attributes(LocalSourceMeshCopy);
|
|
|
|
if (bNeedsBuildScale)
|
|
{
|
|
FTransform BuildScaleTransform = FTransform::Identity;
|
|
BuildScaleTransform.SetScale3D(BuildSettings->BuildScale3D);
|
|
FStaticMeshOperations::ApplyTransform(LocalSourceMeshCopy, BuildScaleTransform, true /*use correct normal transforms*/);
|
|
}
|
|
|
|
if (bNeedsOtherBuildSettings)
|
|
{
|
|
if (!Attributes.GetTriangleNormals().IsValid() || !Attributes.GetTriangleTangents().IsValid())
|
|
{
|
|
// If these attributes don't exist, create them and compute their values for each triangle
|
|
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(LocalSourceMeshCopy);
|
|
}
|
|
|
|
EComputeNTBsFlags ComputeNTBsOptions = EComputeNTBsFlags::BlendOverlappingNormals;
|
|
ComputeNTBsOptions |= BuildSettings->bRecomputeNormals ? EComputeNTBsFlags::Normals : EComputeNTBsFlags::None;
|
|
if (AssetOptions.bRequestTangents)
|
|
{
|
|
ComputeNTBsOptions |= BuildSettings->bRecomputeTangents ? EComputeNTBsFlags::Tangents : EComputeNTBsFlags::None;
|
|
ComputeNTBsOptions |= BuildSettings->bUseMikkTSpace ? EComputeNTBsFlags::UseMikkTSpace : EComputeNTBsFlags::None;
|
|
}
|
|
ComputeNTBsOptions |= BuildSettings->bComputeWeightedNormals ? EComputeNTBsFlags::WeightedNTBs : EComputeNTBsFlags::None;
|
|
if (AssetOptions.bIgnoreRemoveDegenerates == false)
|
|
{
|
|
ComputeNTBsOptions |= BuildSettings->bRemoveDegenerates ? EComputeNTBsFlags::IgnoreDegenerateTriangles : EComputeNTBsFlags::None;
|
|
}
|
|
|
|
FStaticMeshOperations::ComputeTangentsAndNormals(LocalSourceMeshCopy, ComputeNTBsOptions);
|
|
}
|
|
|
|
SourceMesh = &LocalSourceMeshCopy;
|
|
}
|
|
|
|
FMeshDescriptionToDynamicMesh Converter;
|
|
Converter.Convert(SourceMesh, OutMesh, AssetOptions.bRequestTangents);
|
|
|
|
bSuccess = true;
|
|
#else
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromAsset_EditorOnly", "Source Models are not available at Runtime");
|
|
#endif
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
|
|
static bool CopyMeshFromStaticMesh_RenderData(
|
|
UStaticMesh* FromStaticMeshAsset,
|
|
UStaticMeshComponent* StaticMeshComponent,
|
|
FStaticMeshConversionOptions AssetOptions,
|
|
EMeshLODType LODType,
|
|
int32 LODIndex,
|
|
FDynamicMesh3& OutMesh,
|
|
FText& OutErrorMessage
|
|
)
|
|
{
|
|
using namespace ::UE::Geometry;
|
|
|
|
OutMesh.Clear();
|
|
|
|
if (LODType != EMeshLODType::MaxAvailable && LODType != EMeshLODType::RenderData)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMeshRender_LODNotAvailable", "Requested LOD Type is not available");
|
|
return false;
|
|
}
|
|
|
|
#if !WITH_EDITOR
|
|
if (FromStaticMeshAsset->bAllowCPUAccess == false)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_CPUAccess", "StaticMesh bAllowCPUAccess must be set to true to read mesh data at Runtime");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
int32 UseLODIndex = FMath::Clamp(LODIndex, 0, FromStaticMeshAsset->GetNumLODs() - 1);
|
|
|
|
const FStaticMeshLODResources* LODResources = nullptr;
|
|
if (FStaticMeshRenderData* RenderData = FromStaticMeshAsset->GetRenderData())
|
|
{
|
|
LODResources = &RenderData->LODResources[UseLODIndex];
|
|
}
|
|
if (LODResources == nullptr)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_NoLODResources", "LOD Data is not available");
|
|
return false;
|
|
}
|
|
|
|
FStaticMeshLODResourcesToDynamicMesh::ConversionOptions ConvertOptions;
|
|
#if WITH_EDITOR
|
|
if (AssetOptions.bUseBuildScale)
|
|
{
|
|
// respect BuildScale build setting
|
|
const FMeshBuildSettings& LODBuildSettings = FromStaticMeshAsset->GetSourceModel(UseLODIndex).BuildSettings;
|
|
ConvertOptions.BuildScale = (FVector3d)LODBuildSettings.BuildScale3D;
|
|
}
|
|
#else
|
|
if (!AssetOptions.bUseBuildScale)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMesh_BuildScaleAlreadyBaked", "Requested mesh without BuildScale, but BuildScale is already baked into the RenderData.");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
FStaticMeshLODResourcesToDynamicMesh Converter;
|
|
if (AssetOptions.bRequestInstanceVertexColors && StaticMeshComponent && StaticMeshComponent->LODData.IsValidIndex(UseLODIndex))
|
|
{
|
|
FStaticMeshComponentLODInfo* InstanceMeshLODInfo = &StaticMeshComponent->LODData[UseLODIndex];
|
|
const bool bValidInstanceData = InstanceMeshLODInfo
|
|
&& InstanceMeshLODInfo->OverrideVertexColors
|
|
&& InstanceMeshLODInfo->OverrideVertexColors->GetAllowCPUAccess()
|
|
&& InstanceMeshLODInfo->OverrideVertexColors->GetNumVertices() == LODResources->GetNumVertices();
|
|
Converter.Convert(LODResources, ConvertOptions, OutMesh, bValidInstanceData,
|
|
[InstanceMeshLODInfo](int32 LODVID)
|
|
{
|
|
return InstanceMeshLODInfo->OverrideVertexColors->VertexColor(LODVID);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Converter.Convert(LODResources, ConvertOptions, OutMesh);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool CopyMeshFromStaticMesh(
|
|
UStaticMesh* FromStaticMeshAsset,
|
|
UStaticMeshComponent* StaticMeshComponent,
|
|
FStaticMeshConversionOptions AssetOptions,
|
|
EMeshLODType LODType,
|
|
int32 LODIndex,
|
|
bool bUseClosestLOD,
|
|
FDynamicMesh3& OutMesh,
|
|
FText& OutErrorMessage
|
|
)
|
|
{
|
|
if (!FromStaticMeshAsset)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromStaticMeshRender_NullMesh", "Static Mesh is null");
|
|
return false;
|
|
}
|
|
|
|
if (bUseClosestLOD)
|
|
{
|
|
// attempt to detect if an unavailable LOD was requested, and if so re-map to an available one
|
|
if (LODType == EMeshLODType::MaxAvailable || LODType == EMeshLODType::HiResSourceModel)
|
|
{
|
|
LODIndex = 0;
|
|
}
|
|
#if WITH_EDITOR
|
|
if (LODType == EMeshLODType::MaxAvailable)
|
|
{
|
|
LODType = EMeshLODType::HiResSourceModel;
|
|
}
|
|
if (LODType == EMeshLODType::HiResSourceModel && !FromStaticMeshAsset->IsHiResMeshDescriptionValid())
|
|
{
|
|
LODType = EMeshLODType::SourceModel;
|
|
}
|
|
if (LODType == EMeshLODType::SourceModel)
|
|
{
|
|
LODIndex = FMath::Clamp(LODIndex, 0, FromStaticMeshAsset->GetNumSourceModels() - 1);
|
|
if (!FromStaticMeshAsset->GetSourceModel(LODIndex).IsSourceModelInitialized())
|
|
{
|
|
LODType = EMeshLODType::RenderData;
|
|
}
|
|
}
|
|
if (LODType == EMeshLODType::RenderData)
|
|
{
|
|
LODIndex = FMath::Clamp(LODIndex, 0, FromStaticMeshAsset->GetNumLODs() - 1);
|
|
}
|
|
#else
|
|
LODType = EMeshLODType::RenderData;
|
|
LODIndex = FMath::Clamp(LODIndex, 0, FromStaticMeshAsset->GetNumLODs() - 1);
|
|
#endif
|
|
}
|
|
|
|
if (LODType == EMeshLODType::RenderData)
|
|
{
|
|
return CopyMeshFromStaticMesh_RenderData(FromStaticMeshAsset, StaticMeshComponent, AssetOptions, LODType, LODIndex, OutMesh, OutErrorMessage);
|
|
}
|
|
else
|
|
{
|
|
return CopyMeshFromStaticMesh_SourceData(FromStaticMeshAsset, AssetOptions, LODType, LODIndex, OutMesh, OutErrorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool SceneComponentToDynamicMesh(USceneComponent* Component, const FToMeshOptions& Options, bool bTransformToWorld,
|
|
Geometry::FDynamicMesh3& OutMesh, FTransform& OutLocalToWorld, FText& OutErrorMessage,
|
|
TArray<UMaterialInterface*>* OutComponentMaterials, TArray<UMaterialInterface*>* OutAssetMaterials)
|
|
{
|
|
using namespace ::UE::Geometry;
|
|
|
|
bool bSuccess = false;
|
|
OutMesh.Clear();
|
|
|
|
if (!Component)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_NullComponent", "Scene Component is null");
|
|
return false;
|
|
}
|
|
OutLocalToWorld = Component->GetComponentTransform();
|
|
|
|
auto GetPrimitiveComponentMaterials = [](UPrimitiveComponent* PrimComp, TArray<UMaterialInterface*>& Materials)
|
|
{
|
|
int32 NumMaterials = PrimComp->GetNumMaterials();
|
|
Materials.SetNum(NumMaterials);
|
|
for (int32 k = 0; k < NumMaterials; ++k)
|
|
{
|
|
Materials[k] = PrimComp->GetMaterial(k);
|
|
}
|
|
};
|
|
|
|
// if Component Materials were requested, try to get them generically off the primitive component
|
|
// Note: Currently all supported types happen to be primitive components as well; will need to update if this changes
|
|
if (OutComponentMaterials)
|
|
{
|
|
OutComponentMaterials->Empty();
|
|
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component))
|
|
{
|
|
GetPrimitiveComponentMaterials(PrimComp, *OutComponentMaterials);
|
|
}
|
|
}
|
|
|
|
|
|
if (USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(Component))
|
|
{
|
|
const int32 NumLODs = SkinnedMeshComponent->GetNumLODs();
|
|
int32 RequestedLOD = Options.LODType == EMeshLODType::MaxAvailable ? 0 : Options.LODIndex;
|
|
if (Options.bUseClosestLOD)
|
|
{
|
|
RequestedLOD = FMath::Clamp(RequestedLOD, 0, NumLODs - 1);
|
|
}
|
|
if (RequestedLOD < 0 || RequestedLOD > NumLODs - 1)
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingSkinnedMeshComponentLOD", "SkinnedMeshComponent requested LOD does not exist");
|
|
}
|
|
else
|
|
{
|
|
USkinnedAsset* SkinnedAsset = SkinnedMeshComponent->GetSkinnedAsset();
|
|
if (SkinnedAsset)
|
|
{
|
|
SkinnedMeshComponentToDynamicMesh(*SkinnedMeshComponent, OutMesh, RequestedLOD, Options.bWantTangents);
|
|
OutMesh.DiscardTriangleGroups();
|
|
|
|
if (OutAssetMaterials)
|
|
{
|
|
const TArray<FSkeletalMaterial>& Materials = SkinnedAsset->GetMaterials();
|
|
OutAssetMaterials->SetNum(Materials.Num());
|
|
for (int32 k = 0; k < Materials.Num(); ++k)
|
|
{
|
|
(*OutAssetMaterials)[k] = Materials[k].MaterialInterface;
|
|
}
|
|
}
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingSkinnedAsset", "SkinnedMeshComponent has a null SkinnedAsset");
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(Component))
|
|
{
|
|
UStaticMesh* StaticMesh = SplineMeshComponent->GetStaticMesh();
|
|
if (StaticMesh)
|
|
{
|
|
Private::ConversionHelper::FStaticMeshConversionOptions AssetOptions;
|
|
AssetOptions.bApplyBuildSettings = (Options.bWantNormals || Options.bWantTangents);
|
|
AssetOptions.bRequestTangents = Options.bWantTangents;
|
|
AssetOptions.bRequestInstanceVertexColors = Options.bWantInstanceColors;
|
|
bSuccess = Private::ConversionHelper::CopyMeshFromStaticMesh(
|
|
StaticMesh, SplineMeshComponent, AssetOptions, Options.LODType, Options.LODIndex, Options.bUseClosestLOD, OutMesh, OutErrorMessage);
|
|
|
|
// deform the dynamic mesh and its tangent space with the spline
|
|
if (bSuccess)
|
|
{
|
|
const bool bUpdateTangentSpace = Options.bWantTangents;
|
|
SplineDeformDynamicMesh(*SplineMeshComponent, OutMesh, bUpdateTangentSpace);
|
|
|
|
if (OutAssetMaterials)
|
|
{
|
|
int32 NumMaterials = StaticMesh->GetStaticMaterials().Num();
|
|
OutAssetMaterials->SetNum(NumMaterials);
|
|
for (int32 k = 0; k < NumMaterials; ++k)
|
|
{
|
|
(*OutAssetMaterials)[k] = StaticMesh->GetMaterial(k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromSplineMeshComponent_MissingStaticMesh", "SplineMeshComponent has a null StaticMesh");
|
|
}
|
|
}
|
|
else if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
|
|
{
|
|
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if (StaticMesh)
|
|
{
|
|
Private::ConversionHelper::FStaticMeshConversionOptions AssetOptions;
|
|
AssetOptions.bApplyBuildSettings = (Options.bWantNormals || Options.bWantTangents);
|
|
AssetOptions.bRequestTangents = Options.bWantTangents;
|
|
AssetOptions.bRequestInstanceVertexColors = Options.bWantInstanceColors;
|
|
bSuccess = Private::ConversionHelper::CopyMeshFromStaticMesh(
|
|
StaticMesh, StaticMeshComponent, AssetOptions, Options.LODType, Options.LODIndex, Options.bUseClosestLOD, OutMesh, OutErrorMessage);
|
|
|
|
// if we have an ISMC, append instances
|
|
if (UInstancedStaticMeshComponent* ISMComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent))
|
|
{
|
|
FDynamicMesh3 InstancedMesh = MoveTemp(OutMesh);
|
|
OutMesh.Clear();
|
|
|
|
FDynamicMesh3 AccumMesh;
|
|
AccumMesh.EnableMatchingAttributes(InstancedMesh);
|
|
FDynamicMeshEditor Editor(&AccumMesh);
|
|
FMeshIndexMappings Mappings;
|
|
|
|
int32 NumInstances = ISMComponent->GetInstanceCount();
|
|
for (int32 InstanceIdx = 0; InstanceIdx < NumInstances; ++InstanceIdx)
|
|
{
|
|
if (ISMComponent->IsValidInstance(InstanceIdx))
|
|
{
|
|
FTransform InstanceTransform;
|
|
ISMComponent->GetInstanceTransform(InstanceIdx, InstanceTransform, /*bWorldSpace=*/false);
|
|
FTransformSRT3d XForm(InstanceTransform);
|
|
|
|
Mappings.Reset();
|
|
Editor.AppendMesh(&InstancedMesh, Mappings,
|
|
[&](int, const FVector3d& Position) { return XForm.TransformPosition(Position); },
|
|
[&](int, const FVector3d& Normal) { return XForm.TransformNormal(Normal); });
|
|
}
|
|
}
|
|
|
|
OutMesh = MoveTemp(AccumMesh);
|
|
}
|
|
|
|
if (OutAssetMaterials)
|
|
{
|
|
int32 NumMaterials = StaticMesh->GetStaticMaterials().Num();
|
|
OutAssetMaterials->SetNum(NumMaterials);
|
|
for (int32 k = 0; k < NumMaterials; ++k)
|
|
{
|
|
(*OutAssetMaterials)[k] = StaticMesh->GetMaterial(k);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingStaticMesh", "StaticMeshComponent has a null StaticMesh");
|
|
}
|
|
}
|
|
else if (UDynamicMeshComponent* DynamicMeshComponent = Cast<UDynamicMeshComponent>(Component))
|
|
{
|
|
UDynamicMesh* CopyDynamicMesh = DynamicMeshComponent->GetDynamicMesh();
|
|
if (CopyDynamicMesh)
|
|
{
|
|
CopyDynamicMesh->ProcessMesh([&](const FDynamicMesh3& Mesh)
|
|
{
|
|
OutMesh = Mesh;
|
|
});
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingDynamicMesh", "DynamicMeshComponent has a null DynamicMesh");
|
|
}
|
|
}
|
|
else if (UBrushComponent* BrushComponent = Cast<UBrushComponent>(Component))
|
|
{
|
|
FVolumeToMeshOptions VolOptions;
|
|
VolOptions.bMergeVertices = true;
|
|
VolOptions.bAutoRepairMesh = true;
|
|
VolOptions.bOptimizeMesh = true;
|
|
VolOptions.bSetGroups = true;
|
|
|
|
OutMesh.EnableTriangleGroups();
|
|
BrushComponentToDynamicMesh(BrushComponent, OutMesh, VolOptions);
|
|
|
|
// compute normals for current polygroup topology
|
|
OutMesh.EnableAttributes();
|
|
if (Options.bWantNormals)
|
|
{
|
|
FDynamicMeshNormalOverlay* Normals = OutMesh.Attributes()->PrimaryNormals();
|
|
FMeshNormals::InitializeOverlayTopologyFromFaceGroups(&OutMesh, Normals);
|
|
FMeshNormals::QuickRecomputeOverlayNormals(OutMesh);
|
|
}
|
|
|
|
if (OutMesh.TriangleCount() > 0)
|
|
{
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_InvalidBrushConversion", "BrushComponent conversion produced 0 triangles");
|
|
}
|
|
}
|
|
else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast<UGeometryCollectionComponent>(Component))
|
|
{
|
|
if (const UGeometryCollection* RestCollection = GeometryCollectionComponent->GetRestCollection())
|
|
{
|
|
if (const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get())
|
|
{
|
|
FTransform UnusedTransform;
|
|
const TArray<FTransform3f>& DynamicTransforms = GeometryCollectionComponent->GetComponentSpaceTransforms3f();
|
|
if (!DynamicTransforms.IsEmpty())
|
|
{
|
|
ConvertGeometryCollectionToDynamicMesh(OutMesh, UnusedTransform, false, *Collection, true, DynamicTransforms, false, Collection->TransformIndex.GetConstArray());
|
|
}
|
|
else
|
|
{
|
|
ConvertGeometryCollectionToDynamicMesh(OutMesh, UnusedTransform, false, *Collection, true, TArrayView<const FTransform3f>(Collection->Transform.GetConstArray()), true, Collection->TransformIndex.GetConstArray());
|
|
}
|
|
bSuccess = true;
|
|
|
|
if (OutAssetMaterials)
|
|
{
|
|
//const TArray<TObjectPtr<UMaterialInterface>>& AssetMaterials = RestCollection->Materials;
|
|
*OutAssetMaterials = RestCollection->Materials;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingCollectionData", "GeometryCollectionComponent has null Geometry Collection data");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutErrorMessage = LOCTEXT("CopyMeshFromComponent_MissingRestCollection", "GeometryCollectionComponent has null Rest Collection object");
|
|
}
|
|
}
|
|
|
|
// transform mesh to world
|
|
if (bSuccess && bTransformToWorld)
|
|
{
|
|
MeshTransforms::ApplyTransform(OutMesh, (FTransformSRT3d)OutLocalToWorld, true);
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
} // end namespace Conversion
|
|
} // end namespace UE
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|