// 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(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(Component)) { return true; } else if (const UStaticMeshComponent* StaticMeshComponent = Cast(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(Component)) { return true; } else if (Cast(Component)) { return true; } else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast(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* OutComponentMaterials, TArray* 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& 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(Component)) { GetPrimitiveComponentMaterials(PrimComp, *OutComponentMaterials); } } if (USkinnedMeshComponent* SkinnedMeshComponent = Cast(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& 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(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(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(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(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(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(Component)) { if (const UGeometryCollection* RestCollection = GeometryCollectionComponent->GetRestCollection()) { if (const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get()) { FTransform UnusedTransform; const TArray& 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(Collection->Transform.GetConstArray()), true, Collection->TransformIndex.GetConstArray()); } bSuccess = true; if (OutAssetMaterials) { //const TArray>& 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