2023-12-15 16:11:52 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "ConversionUtils/SceneComponentToDynamicMesh.h"
# include "Engine/StaticMesh.h"
# include "Engine/SkeletalMesh.h"
# include "Engine/SkinnedAssetCommon.h"
2024-03-06 12:02:31 -05:00
# include "UObject/Package.h"
2023-12-15 16:11:52 -05:00
# 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"
2024-02-02 11:04:32 -05:00
# include "StaticMeshComponentLODInfo.h"
2023-12-15 16:11:52 -05:00
# 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"
2024-09-19 19:06:01 -04:00
# include "SkeletalMeshOperations.h"
2023-12-15 16:11:52 -05:00
# 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 ;
}
2024-03-06 12:02:31 -05:00
else if ( const USkinnedMeshComponent * SkinnedMeshComponent = Cast < USkinnedMeshComponent > ( Component ) )
2023-12-15 16:11:52 -05:00
{
2024-03-06 12:02:31 -05:00
# if WITH_EDITOR
const USkinnedAsset * SkinnedAsset = ( ! SkinnedMeshComponent - > IsUnreachable ( ) & & SkinnedMeshComponent - > IsValidLowLevel ( ) ) ? SkinnedMeshComponent - > GetSkinnedAsset ( ) : nullptr ;
2024-03-20 18:18:17 -04:00
return SkinnedAsset & & ! SkinnedAsset - > GetOutermost ( ) - > bIsCookedForEditor ;
2024-03-06 12:02:31 -05:00
# else
2023-12-15 16:11:52 -05:00
return true ;
2024-03-06 12:02:31 -05:00
# endif
2023-12-15 16:11:52 -05:00
}
else if ( Cast < USplineMeshComponent > ( Component ) )
{
return true ;
}
2024-03-06 12:02:31 -05:00
else if ( const UStaticMeshComponent * StaticMeshComponent = Cast < UStaticMeshComponent > ( Component ) )
2023-12-15 16:11:52 -05:00
{
2024-03-06 12:02:31 -05:00
# if WITH_EDITOR
const UStaticMesh * StaticMesh = ( ! StaticMeshComponent - > IsUnreachable ( ) & & StaticMeshComponent - > IsValidLowLevel ( ) ) ? StaticMeshComponent - > GetStaticMesh ( ) : nullptr ;
return StaticMesh & & ! StaticMesh - > GetOutermost ( ) - > bIsCookedForEditor ;
# else
2023-12-15 16:11:52 -05:00
return true ;
2024-03-06 12:02:31 -05:00
# endif
2023-12-15 16:11:52 -05:00
}
else if ( Cast < UDynamicMeshComponent > ( Component ) )
{
return true ;
}
else if ( Cast < UBrushComponent > ( Component ) )
{
return true ;
}
2024-03-20 18:18:17 -04:00
else if ( UGeometryCollectionComponent * GeometryCollectionComponent = Cast < UGeometryCollectionComponent > ( Component ) )
2023-12-15 16:11:52 -05:00
{
2024-03-20 18:18:17 -04:00
# if WITH_EDITOR
const UGeometryCollection * GeometryCollectionAsset = ( ! GeometryCollectionComponent - > IsUnreachable ( ) & & GeometryCollectionComponent - > IsValidLowLevel ( ) ) ? GeometryCollectionComponent - > GetRestCollection ( ) : nullptr ;
return GeometryCollectionAsset & & ! GeometryCollectionAsset - > GetOutermost ( ) - > bIsCookedForEditor ;
# else
2023-12-15 16:11:52 -05:00
return true ;
2024-03-20 18:18:17 -04:00
# endif
2023-12-15 16:11:52 -05:00
}
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
2024-03-04 18:33:07 -05:00
// helper for the material ID remapping used for source LODs
// note: returns empty array if no remapping needed (or if not WITH_EDITOR)
TArray < int32 > MapSectionToMaterialID ( const UStaticMesh * Mesh , int32 SourceLOD , bool bHighResLOD )
{
# if WITH_EDITOR
check ( Mesh ) ;
TMap < int32 , int32 > SectionToMaterial ;
const int32 NumMaterials = Mesh - > GetStaticMaterials ( ) . Num ( ) ;
2024-04-30 13:14:18 -04:00
int32 NumSectionIndex = 0 ;
2024-03-04 18:33:07 -05:00
if ( bHighResLOD )
{
// custom path for HiResSource, where the section info map isn't available so we use mesh description slot names
// (note that in practice this info seems to be incorrect for some meshes; prefer the section info map where available)
const FMeshDescription * MeshDescription = Mesh - > GetHiResMeshDescription ( ) ;
if ( ! MeshDescription )
{
// fall back to empty array (treated as identity map)
return TArray < int32 > ( ) ;
}
const FStaticMeshConstAttributes MeshDescriptionAttributes ( * MeshDescription ) ;
TPolygonGroupAttributesConstRef < FName > MaterialSlotNames = MeshDescriptionAttributes . GetPolygonGroupMaterialSlotNames ( ) ;
int32 SectionIndex = 0 ;
for ( FPolygonGroupID PolygonGroupID : MeshDescription - > PolygonGroups ( ) . GetElementIDs ( ) )
{
2024-04-30 13:14:18 -04:00
int32 MaterialIndex = PolygonGroupID > = 0 & & PolygonGroupID < MaterialSlotNames . GetNumElements ( ) ? Mesh - > GetStaticMaterials ( ) . IndexOfByPredicate (
2024-03-04 18:33:07 -05:00
[ & MaterialSlotName = MaterialSlotNames [ PolygonGroupID ] ] ( const FStaticMaterial & StaticMaterial ) { return StaticMaterial . MaterialSlotName = = MaterialSlotName ; }
2024-04-30 13:14:18 -04:00
) : INDEX_NONE ;
2024-03-04 18:33:07 -05:00
if ( MaterialIndex ! = INDEX_NONE )
{
SectionToMaterial . Add ( SectionIndex , MaterialIndex ) ;
}
+ + SectionIndex ;
}
2024-04-30 13:14:18 -04:00
NumSectionIndex = SectionIndex ;
2024-03-04 18:33:07 -05:00
}
else
{
int32 UseLOD = SourceLOD ;
const FMeshSectionInfoMap & SectionMap = Mesh - > GetSectionInfoMap ( ) ;
int32 LODSectionNum = SectionMap . GetSectionNumber ( UseLOD ) ;
TArray < int32 > Result ;
for ( int32 SectionIndex = 0 ; SectionIndex < LODSectionNum ; + + SectionIndex )
{
if ( SectionMap . IsValidSection ( UseLOD , SectionIndex ) )
{
int32 MaterialIndex = SectionMap . Get ( UseLOD , SectionIndex ) . MaterialIndex ;
SectionToMaterial . Add ( SectionIndex , MaterialIndex ) ;
}
}
2024-04-30 13:14:18 -04:00
NumSectionIndex = LODSectionNum ;
2024-03-04 18:33:07 -05:00
}
TArray < int32 > Result ;
2024-04-30 13:14:18 -04:00
Result . SetNumUninitialized ( NumSectionIndex ) ;
2024-03-04 18:33:07 -05:00
// Fill in identity mapping first to cover any unmapped indices
for ( int32 Idx = 0 ; Idx < Result . Num ( ) ; + + Idx )
{
Result [ Idx ] = Idx ;
}
for ( TPair < int32 , int32 > SectionMaterial : SectionToMaterial )
{
Result [ SectionMaterial . Key ] = FMath : : Clamp ( SectionMaterial . Value , 0 , NumMaterials - 1 ) ;
}
return Result ;
# else
return TArray < int32 > ( ) ;
# endif
}
2023-12-15 16:11:52 -05:00
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 ;
2024-03-04 18:33:07 -05:00
TArray < int32 > PolygonGroupToMaterialMap = GetPolygonGroupToMaterialIndexMap ( FromStaticMeshAsset , LODType , LODIndex ) ;
2023-12-15 16:11:52 -05:00
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 ;
2024-09-11 17:57:34 -04:00
Converter . bVIDsFromNonManifoldMeshDescriptionAttr = AssetOptions . bIncludeNonManifoldSrcInfo ;
2024-04-29 16:57:22 -04:00
if ( ! AssetOptions . bUseSectionMaterialIndices )
{
Converter . SetPolygonGroupToMaterialIndexMap ( PolygonGroupToMaterialMap ) ;
}
2023-12-15 16:11:52 -05:00
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 ,
2024-02-02 11:04:32 -05:00
UStaticMeshComponent * StaticMeshComponent ,
2023-12-15 16:11:52 -05:00
FStaticMeshConversionOptions AssetOptions ,
EMeshLODType LODType ,
int32 LODIndex ,
2024-03-27 15:55:30 -04:00
bool bRequestInstanceVertexColors ,
2023-12-15 16:11:52 -05:00
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
2024-08-28 15:41:01 -04:00
const bool bIsSourceModelValid = FromStaticMeshAsset - > IsSourceModelValid ( UseLODIndex ) ;
if ( AssetOptions . bUseBuildScale & & bIsSourceModelValid )
2023-12-15 16:11:52 -05:00
{
// respect BuildScale build setting
const FMeshBuildSettings & LODBuildSettings = FromStaticMeshAsset - > GetSourceModel ( UseLODIndex ) . BuildSettings ;
ConvertOptions . BuildScale = ( FVector3d ) LODBuildSettings . BuildScale3D ;
}
2024-08-28 15:41:01 -04:00
// In case of cooked editor, Source model won't be valid, so it will follow the same rules as the runtime path.
else if ( ! AssetOptions . bUseBuildScale & & ! bIsSourceModelValid )
2023-12-15 16:11:52 -05:00
# else
if ( ! AssetOptions . bUseBuildScale )
2024-08-28 15:41:01 -04:00
# endif
2023-12-15 16:11:52 -05:00
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromStaticMesh_BuildScaleAlreadyBaked " , " Requested mesh without BuildScale, but BuildScale is already baked into the RenderData. " ) ;
return false ;
}
FStaticMeshLODResourcesToDynamicMesh Converter ;
2024-03-27 15:55:30 -04:00
if ( bRequestInstanceVertexColors & & StaticMeshComponent & & StaticMeshComponent - > LODData . IsValidIndex ( UseLODIndex ) )
2024-02-02 11:04:32 -05:00
{
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 ) ;
}
2023-12-15 16:11:52 -05:00
return true ;
}
static bool CopyMeshFromStaticMesh (
UStaticMesh * FromStaticMeshAsset ,
2024-02-02 11:04:32 -05:00
UStaticMeshComponent * StaticMeshComponent ,
2023-12-15 16:11:52 -05:00
FStaticMeshConversionOptions AssetOptions ,
EMeshLODType LODType ,
int32 LODIndex ,
bool bUseClosestLOD ,
2024-03-27 15:55:30 -04:00
bool bRequestInstanceVertexColors ,
2023-12-15 16:11:52 -05:00
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 ) ;
}
2023-12-15 20:03:56 -05:00
# else
LODType = EMeshLODType : : RenderData ;
LODIndex = FMath : : Clamp ( LODIndex , 0 , FromStaticMeshAsset - > GetNumLODs ( ) - 1 ) ;
# endif
2023-12-15 16:11:52 -05:00
}
if ( LODType = = EMeshLODType : : RenderData )
{
2024-03-27 15:55:30 -04:00
return CopyMeshFromStaticMesh_RenderData ( FromStaticMeshAsset , StaticMeshComponent , AssetOptions , LODType , LODIndex , bRequestInstanceVertexColors , OutMesh , OutErrorMessage ) ;
2023-12-15 16:11:52 -05:00
}
else
{
return CopyMeshFromStaticMesh_SourceData ( FromStaticMeshAsset , AssetOptions , LODType , LODIndex , OutMesh , OutErrorMessage ) ;
}
}
2024-09-19 19:06:01 -04:00
static bool CopyMeshFromSkinnedAsset (
USkinnedAsset * FromSkinnedAsset ,
USkinnedMeshComponent * SkinnedMeshComponent ,
EMeshLODType LODType ,
int32 LODIndex ,
bool bUseClosestLOD ,
bool bWantTangents ,
FDynamicMesh3 & OutMesh ,
FText & OutErrorMessage
)
{
if ( ! FromSkinnedAsset )
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromSkinnedAsset_NullMesh " , " Skinned mesh is null " ) ;
return false ;
}
USkeletalMesh * SkeletalMesh = Cast < USkeletalMesh > ( FromSkinnedAsset ) ;
// If using non-skeletal mesh variations of skinned meshes, just go straight to render data.
if ( ! SkeletalMesh )
{
LODType = EMeshLODType : : RenderData ;
}
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 )
{
LODType = EMeshLODType : : SourceModel ;
}
if ( LODType = = EMeshLODType : : SourceModel )
{
LODIndex = FMath : : Clamp ( LODIndex , 0 , SkeletalMesh - > GetNumSourceModels ( ) - 1 ) ;
if ( ! SkeletalMesh - > GetSourceModel ( LODIndex ) . HasMeshDescription ( ) )
{
LODType = EMeshLODType : : RenderData ;
}
}
if ( LODType = = EMeshLODType : : RenderData )
{
LODIndex = FMath : : Clamp ( LODIndex , 0 , FromSkinnedAsset - > GetLODNum ( ) - 1 ) ;
}
# else
LODType = EMeshLODType : : RenderData ;
LODIndex = FMath : : Clamp ( LODIndex , 0 , FromSkinnedAsset - > GetLODNum ( ) - 1 ) ;
# endif
}
if ( LODType = = EMeshLODType : : RenderData )
{
return SkinnedMeshComponentToDynamicMesh ( * SkinnedMeshComponent , OutMesh , LODIndex , bWantTangents ) ;
}
else
{
# if WITH_EDITOR
const FMeshDescription * SourceMesh = nullptr ;
// Check first if we have bulk data available and non-empty.
if ( SkeletalMesh - > HasMeshDescription ( LODIndex ) )
{
SourceMesh = SkeletalMesh - > GetMeshDescription ( LODIndex ) ;
}
if ( SourceMesh = = nullptr )
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromSkinnedAsset_LODNotAvailable " , " Requested LOD source mesh is not available " ) ;
return false ;
}
TMap < FName , float > MorphTargetWeights ;
for ( const TPair < const UMorphTarget * , int32 > & MorphTarget : SkinnedMeshComponent - > ActiveMorphTargets )
{
const FName MorphName = MorphTarget . Key - > GetFName ( ) ;
const float MorphWeight = SkinnedMeshComponent - > MorphTargetWeights [ MorphTarget . Value ] ;
MorphTargetWeights . Add ( MorphName , MorphWeight ) ;
}
const TArray < FTransform > & ComponentSpaceTransforms = SkinnedMeshComponent - > GetComponentSpaceTransforms ( ) ;
FMeshDescription DeformedMesh ;
if ( ! FSkeletalMeshOperations : : GetPosedMesh ( * SourceMesh , DeformedMesh , ComponentSpaceTransforms , NAME_None , MorphTargetWeights ) )
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromSkinnedAsset_CannotPose " , " Unable to pose the source mesh " ) ;
return false ;
}
FDynamicMesh3 NewMesh ;
FMeshDescriptionToDynamicMesh Converter ;
// Leave this on, since the set morph target node uses this.
Converter . bVIDsFromNonManifoldMeshDescriptionAttr = true ;
Converter . Convert ( & DeformedMesh , OutMesh , bWantTangents ) ;
return true ;
# else
OutErrorMessage = LOCTEXT ( " CopyMeshFromSkinnedAsset_EditorOnly " , " Source Models are not available at Runtime " ) ;
return false ;
# endif
}
}
2023-12-15 16:11:52 -05:00
}
2024-03-04 18:33:07 -05:00
TArray < int32 > GetPolygonGroupToMaterialIndexMap ( const UStaticMesh * StaticMesh , EMeshLODType LODType , int32 LODIndex )
{
# if WITH_EDITOR
if ( LODType = = EMeshLODType : : RenderData )
{
// don't need to remap material indices for render LODs
return TArray < int32 > ( ) ;
}
// map the 'max available' lod type
if ( LODType = = EMeshLODType : : MaxAvailable )
{
LODType = StaticMesh - > IsHiResMeshDescriptionValid ( ) ? EMeshLODType : : HiResSourceModel : EMeshLODType : : SourceModel ;
LODIndex = 0 ;
}
return Private : : ConversionHelper : : MapSectionToMaterialID ( StaticMesh , LODIndex , LODType = = EMeshLODType : : HiResSourceModel ) ;
# else
return TArray < int32 > ( ) ;
# endif
}
2023-12-15 16:11:52 -05:00
2024-03-27 15:55:30 -04:00
bool StaticMeshToDynamicMesh ( UStaticMesh * InMesh , Geometry : : FDynamicMesh3 & OutMesh , FText & OutErrorMessage ,
const FStaticMeshConversionOptions & ConversionOptions , EMeshLODType LODType , int32 LODIndex , bool bUseClosestLOD )
{
constexpr UStaticMeshComponent * StaticMeshComponent = nullptr ; // ok to leave this null when converting from asset
constexpr bool bRequestInstanceVertexColors = false ; // cannot request instance colors from the asset
return Private : : ConversionHelper : : CopyMeshFromStaticMesh (
InMesh , StaticMeshComponent , ConversionOptions , LODType , LODIndex , bUseClosestLOD , bRequestInstanceVertexColors , OutMesh , OutErrorMessage ) ;
}
2023-12-15 16:11:52 -05:00
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 ( ) ;
2024-02-02 11:04:32 -05:00
if ( ! Component )
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromComponent_NullComponent " , " Scene Component is null " ) ;
return false ;
}
2023-12-15 16:11:52 -05:00
OutLocalToWorld = Component - > GetComponentTransform ( ) ;
2024-02-15 23:01:14 -05:00
auto GetPrimitiveComponentMaterials = [ ] ( UPrimitiveComponent * PrimComp , TArray < UMaterialInterface * > & Materials )
2023-12-15 16:11:52 -05:00
{
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
{
2024-09-19 19:06:01 -04:00
if ( USkinnedAsset * SkinnedAsset = SkinnedMeshComponent - > GetSkinnedAsset ( ) )
2023-12-15 16:11:52 -05:00
{
2024-09-19 19:06:01 -04:00
bSuccess = Private : : ConversionHelper : : CopyMeshFromSkinnedAsset ( SkinnedAsset , SkinnedMeshComponent , Options . LODType , Options . LODIndex , Options . bUseClosestLOD , Options . bWantTangents , OutMesh , OutErrorMessage ) ;
if ( bSuccess )
2023-12-15 16:11:52 -05:00
{
2024-09-19 19:06:01 -04:00
OutMesh . DiscardTriangleGroups ( ) ;
if ( OutAssetMaterials )
2023-12-15 16:11:52 -05:00
{
2024-09-19 19:06:01 -04:00
const TArray < FSkeletalMaterial > & Materials = SkinnedAsset - > GetMaterials ( ) ;
OutAssetMaterials - > SetNum ( Materials . Num ( ) ) ;
for ( int32 k = 0 ; k < Materials . Num ( ) ; + + k )
{
( * OutAssetMaterials ) [ k ] = Materials [ k ] . MaterialInterface ;
}
2023-12-15 16:11:52 -05:00
}
}
}
else
{
OutErrorMessage = LOCTEXT ( " CopyMeshFromComponent_MissingSkinnedAsset " , " SkinnedMeshComponent has a null SkinnedAsset " ) ;
}
}
}
else if ( USplineMeshComponent * SplineMeshComponent = Cast < USplineMeshComponent > ( Component ) )
{
UStaticMesh * StaticMesh = SplineMeshComponent - > GetStaticMesh ( ) ;
if ( StaticMesh )
{
2024-03-27 15:55:30 -04:00
FStaticMeshConversionOptions AssetOptions ;
2023-12-15 16:11:52 -05:00
AssetOptions . bApplyBuildSettings = ( Options . bWantNormals | | Options . bWantTangents ) ;
AssetOptions . bRequestTangents = Options . bWantTangents ;
bSuccess = Private : : ConversionHelper : : CopyMeshFromStaticMesh (
2024-03-27 15:55:30 -04:00
StaticMesh , SplineMeshComponent , AssetOptions , Options . LODType , Options . LODIndex , Options . bUseClosestLOD , Options . bWantInstanceColors , OutMesh , OutErrorMessage ) ;
2023-12-15 16:11:52 -05:00
// 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 )
{
2024-03-27 15:55:30 -04:00
FStaticMeshConversionOptions AssetOptions ;
2023-12-15 16:11:52 -05:00
AssetOptions . bApplyBuildSettings = ( Options . bWantNormals | | Options . bWantTangents ) ;
AssetOptions . bRequestTangents = Options . bWantTangents ;
2024-03-27 15:55:30 -04:00
bool bRequestInstanceVertexColors = Options . bWantInstanceColors ;
2023-12-15 16:11:52 -05:00
bSuccess = Private : : ConversionHelper : : CopyMeshFromStaticMesh (
2024-03-27 15:55:30 -04:00
StaticMesh , StaticMeshComponent , AssetOptions , Options . LODType , Options . LODIndex , Options . bUseClosestLOD , bRequestInstanceVertexColors , OutMesh , OutErrorMessage ) ;
2023-12-15 16:11:52 -05:00
// 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