// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= GeometryCollection.cpp: UGeometryCollection methods. =============================================================================*/ #include "GeometryCollection/GeometryCollectionObject.h" #include "GeometryCollection/GeometryCollection.h" #include "GeometryCollection/GeometryCollectionCache.h" #include "GeometryCollection/GeometryCollectionRenderData.h" #include "Materials/Material.h" #include "UObject/DestructionObjectVersion.h" #include "UObject/UE5MainStreamObjectVersion.h" #include "UObject/FortniteMainBranchObjectVersion.h" #include "UObject/ObjectSaveContext.h" #include "Serialization/ArchiveCountMem.h" #include "HAL/IConsoleManager.h" #include "Interfaces/ITargetPlatform.h" #include "UObject/Package.h" #include "Materials/MaterialInstance.h" #include "ProfilingDebugging/CookStats.h" #include "EngineUtils.h" #include "Engine/Engine.h" #include "Engine/StaticMesh.h" #include "PhysicsEngine/PhysicsSettings.h" #include "EditorFramework/AssetImportData.h" #include "Rendering/NaniteResources.h" #include "Engine/AssetUserData.h" #include "PhysicalMaterials/PhysicalMaterial.h" #include "PhysicsProxy/GeometryCollectionPhysicsProxy.h" #include "Chaos/ErrorReporter.h" #if WITH_EDITOR #include "GeometryCollection/DerivedDataGeometryCollectionCooker.h" #include "GeometryCollection/GeometryCollectionConvexUtility.h" #include "GeometryCollection/GeometryCollectionEngineSizeSpecificUtility.h" #include "GeometryCollection/GeometryCollectionComponent.h" #include "DerivedDataCacheInterface.h" #include "Serialization/MemoryReader.h" #include "NaniteBuilder.h" #include "Rendering/NaniteResources.h" // TODO: Temp until new asset-agnostic builder API #include "StaticMeshResources.h" #endif #include "GeometryCollection/GeometryCollectionSimulationCoreTypes.h" #include "Chaos/ChaosArchive.h" #include "Chaos/MassProperties.h" #include "GeometryCollectionProxyData.h" #include "Dataflow/DataflowObject.h" #include "GeometryCollection/Facades/CollectionHierarchyFacade.h" #include "GeometryCollection/Facades/CollectionInstancedMeshFacade.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GeometryCollectionObject) DEFINE_LOG_CATEGORY_STATIC(LogGeometryCollectionInternal, Log, All); bool GeometryCollectionAssetForceStripOnCook = false; FAutoConsoleVariableRef CVarGeometryCollectionBypassPhysicsAttributes( TEXT("p.GeometryCollectionAssetForceStripOnCook"), GeometryCollectionAssetForceStripOnCook, TEXT("Bypass the construction of simulation properties when all bodies are simply cached for playback.")); bool bGeometryCollectionEnableForcedConvexGenerationInSerialize = true; FAutoConsoleVariableRef CVarGeometryCollectionEnableForcedConvexGenerationInSerialize( TEXT("p.GeometryCollectionEnableForcedConvexGenerationInSerialize"), bGeometryCollectionEnableForcedConvexGenerationInSerialize, TEXT("Enable generation of convex geometry on older destruction files.[def:true]")); bool bGeometryCollectionAlwaysRecreateSimulationData = false; FAutoConsoleVariableRef CVarGeometryCollectionAlwaysRecreateSimulationData( TEXT("p.GeometryCollectionAlwaysRecreateSimulationData"), bGeometryCollectionAlwaysRecreateSimulationData, TEXT("always recreate the simulation data even if the simulation data is not marked as dirty - this has runtime cost in editor - only use as a last resort if default has issues [def:false]")); namespace Chaos { namespace CVars { extern CHAOS_API bool bChaosConvexSimplifyUnion; } } #if ENABLE_COOK_STATS namespace GeometryCollectionCookStats { static FCookStats::FDDCResourceUsageStats UsageStats; static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat) { UsageStats.LogStats(AddStat, TEXT("GeometryCollection.Usage"), TEXT("")); }); } #endif static constexpr float DefaultMaxSizeValue = 99999.9; static const FName GeometryCollectionDataflowTerminalNodeName("GeometryCollectionTerminal"); UGeometryCollection::UGeometryCollection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) #if WITH_EDITOR , bManualDataCreate(false) #endif , EnableClustering(true) , ClusterGroupIndex(0) , MaxClusterLevel(100) , DamageModel(EDamageModelTypeEnum::Chaos_Damage_Model_UserDefined_Damage_Threshold) , DamageThreshold({ 500000.f, 50000.f, 5000.f }) , bUseSizeSpecificDamageThreshold(false) , bUseMaterialDamageModifiers(false) , PerClusterOnlyDamageThreshold(false) , ClusterConnectionType(EClusterConnectionTypeEnum::Chaos_MinimalSpanningSubsetDelaunayTriangulation) , ConnectionGraphBoundsFilteringMargin(0) , bUseFullPrecisionUVs(false) , bStripOnCook(false) , bStripRenderDataOnCook(false) , EnableNanite(false) , bEnableNaniteFallback(false) #if WITH_EDITORONLY_DATA , CollisionType_DEPRECATED(ECollisionTypeEnum::Chaos_Volumetric) , ImplicitType_DEPRECATED(EImplicitTypeEnum::Chaos_Implicit_Convex) , MinLevelSetResolution_DEPRECATED(10) , MaxLevelSetResolution_DEPRECATED(10) , MinClusterLevelSetResolution_DEPRECATED(50) , MaxClusterLevelSetResolution_DEPRECATED(50) , CollisionObjectReductionPercentage_DEPRECATED(0.0f) #endif , bDensityFromPhysicsMaterial(false) , CachedDensityFromPhysicsMaterialInGCm3(0) // <=0 value means not cached yet , bMassAsDensity(true) , Mass(2500.0f) , MinimumMassClamp(0.1f) , bImportCollisionFromSource(false) , bOptimizeConvexes(Chaos::CVars::bChaosConvexSimplifyUnion) , bScaleOnRemoval(true) , bRemoveOnMaxSleep(false) , bAutomaticCrumblePartialClusters(true) , MaximumSleepTime(5.0, 10.0) , RemovalDuration(2.5, 5.0) , bSlowMovingAsSleeping(true) , SlowMovingVelocityThreshold(1) , EnableRemovePiecesOnFracture_DEPRECATED(false) , DataflowInstance(this) , GeometryCollection(new FGeometryCollection()) { PersistentGuid = FGuid::NewGuid(); InvalidateCollection(); #if WITH_EDITOR SimulationDataGuid = StateGuid; RenderDataGuid = StateGuid; bStripOnCook = GeometryCollectionAssetForceStripOnCook; #endif PhysicsMaterial = GEngine? GEngine->DefaultPhysMaterial: nullptr; // make sure we have at least one size specific entry SizeSpecificData.AddDefaulted(); // set the default name for the terminal node DataflowInstance.SetDataflowTerminal(GeometryCollectionDataflowTerminalNodeName); } FGeometryCollectionLevelSetData::FGeometryCollectionLevelSetData() : MinLevelSetResolution(5) , MaxLevelSetResolution(10) , MinClusterLevelSetResolution(25) , MaxClusterLevelSetResolution(50) { } FGeometryCollectionCollisionParticleData::FGeometryCollectionCollisionParticleData() : CollisionParticlesFraction(1.0f) , MaximumCollisionParticles(60) { } FGeometryCollectionCollisionTypeData::FGeometryCollectionCollisionTypeData() : CollisionType(ECollisionTypeEnum::Chaos_Volumetric) , ImplicitType(EImplicitTypeEnum::Chaos_Implicit_Convex) , LevelSet() , CollisionParticles() , CollisionObjectReductionPercentage(0.0f) , CollisionMarginFraction(0.f) { } FGeometryCollectionSizeSpecificData::FGeometryCollectionSizeSpecificData() : MaxSize(DefaultMaxSizeValue) , CollisionShapes({ FGeometryCollectionCollisionTypeData()}) #if WITH_EDITORONLY_DATA , CollisionType_DEPRECATED(ECollisionTypeEnum::Chaos_Volumetric) , ImplicitType_DEPRECATED(EImplicitTypeEnum::Chaos_Implicit_Convex) , MinLevelSetResolution_DEPRECATED(5) , MaxLevelSetResolution_DEPRECATED(10) , MinClusterLevelSetResolution_DEPRECATED(25) , MaxClusterLevelSetResolution_DEPRECATED(50) , CollisionObjectReductionPercentage_DEPRECATED(0) , CollisionParticlesFraction_DEPRECATED(1.f) , MaximumCollisionParticles_DEPRECATED(60) #endif , DamageThreshold(5000.0) { } bool FGeometryCollectionSizeSpecificData::Serialize(FArchive& Ar) { Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID); Ar.UsingCustomVersion(FPhysicsObjectVersion::GUID); return false; //We only have this function to mark custom GUID. Still want serialize tagged properties } #if WITH_EDITORONLY_DATA void FGeometryCollectionSizeSpecificData::PostSerialize(const FArchive& Ar) { const int32 PhysicsObjectVersion = Ar.CustomVer(FPhysicsObjectVersion::GUID); const int32 StreamObjectVersion = Ar.CustomVer(FUE5MainStreamObjectVersion::GUID); // make sure to load back the deprecated values in the new structure if necessary // IMPORTANT : this was merge backed in UE4 and PhysicsObjectVersion had to be used, // that's why we need to test both version to make sure backward asset compatibility is maintained if (Ar.IsLoading() && ( StreamObjectVersion < FUE5MainStreamObjectVersion::GeometryCollectionUserDefinedCollisionShapes && PhysicsObjectVersion < FPhysicsObjectVersion::GeometryCollectionUserDefinedCollisionShapes )) { if (CollisionShapes.Num()) { // @todo(chaos destruction collisions) : Add support for many CollisionShapes[0].CollisionType = CollisionType_DEPRECATED; CollisionShapes[0].ImplicitType = ImplicitType_DEPRECATED; CollisionShapes[0].CollisionObjectReductionPercentage = CollisionObjectReductionPercentage_DEPRECATED; CollisionShapes[0].CollisionMarginFraction = UPhysicsSettings::Get()->SolverOptions.CollisionMarginFraction; CollisionShapes[0].CollisionParticles.CollisionParticlesFraction = CollisionParticlesFraction_DEPRECATED; CollisionShapes[0].CollisionParticles.MaximumCollisionParticles = MaximumCollisionParticles_DEPRECATED; CollisionShapes[0].LevelSet.MinLevelSetResolution = MinLevelSetResolution_DEPRECATED; CollisionShapes[0].LevelSet.MaxLevelSetResolution = MaxLevelSetResolution_DEPRECATED; CollisionShapes[0].LevelSet.MinClusterLevelSetResolution = MinClusterLevelSetResolution_DEPRECATED; CollisionShapes[0].LevelSet.MaxClusterLevelSetResolution = MaxClusterLevelSetResolution_DEPRECATED; } } } #endif const FTransform3f& FGeometryCollectionProxyMeshData::GetMeshTransform(int32 MeshIndex) const { if (MeshTransforms.IsValidIndex(MeshIndex)) { return MeshTransforms[MeshIndex]; } return FTransform3f::Identity; } void FillSharedSimulationSizeSpecificData(FSharedSimulationSizeSpecificData& ToData, const FGeometryCollectionSizeSpecificData& FromData) { ToData.MaxSize = FromData.MaxSize; ToData.CollisionShapesData.SetNumUninitialized(FromData.CollisionShapes.Num()); if (FromData.CollisionShapes.Num()) { for (int i = 0; i < FromData.CollisionShapes.Num(); i++) { ToData.CollisionShapesData[i].CollisionType = FromData.CollisionShapes[i].CollisionType; ToData.CollisionShapesData[i].ImplicitType = FromData.CollisionShapes[i].ImplicitType; ToData.CollisionShapesData[i].LevelSetData.MinLevelSetResolution = FromData.CollisionShapes[i].LevelSet.MinLevelSetResolution; ToData.CollisionShapesData[i].LevelSetData.MaxLevelSetResolution = FromData.CollisionShapes[i].LevelSet.MaxLevelSetResolution; ToData.CollisionShapesData[i].LevelSetData.MinClusterLevelSetResolution = FromData.CollisionShapes[i].LevelSet.MinClusterLevelSetResolution; ToData.CollisionShapesData[i].LevelSetData.MaxClusterLevelSetResolution = FromData.CollisionShapes[i].LevelSet.MaxClusterLevelSetResolution; ToData.CollisionShapesData[i].CollisionObjectReductionPercentage = FromData.CollisionShapes[i].CollisionObjectReductionPercentage; ToData.CollisionShapesData[i].CollisionMarginFraction = FromData.CollisionShapes[i].CollisionMarginFraction; ToData.CollisionShapesData[i].CollisionParticleData.CollisionParticlesFraction = FromData.CollisionShapes[i].CollisionParticles.CollisionParticlesFraction; ToData.CollisionShapesData[i].CollisionParticleData.MaximumCollisionParticles = FromData.CollisionShapes[i].CollisionParticles.MaximumCollisionParticles; } } ToData.DamageThreshold = FromData.DamageThreshold; } FGeometryCollectionSizeSpecificData UGeometryCollection::GeometryCollectionSizeSpecificDataDefaults() { FGeometryCollectionSizeSpecificData Data; Data.MaxSize = DefaultMaxSizeValue; if (Data.CollisionShapes.Num()) { Data.CollisionShapes[0].CollisionType = ECollisionTypeEnum::Chaos_Volumetric; Data.CollisionShapes[0].ImplicitType = EImplicitTypeEnum::Chaos_Implicit_Capsule; Data.CollisionShapes[0].LevelSet.MinLevelSetResolution = 5; Data.CollisionShapes[0].LevelSet.MaxLevelSetResolution = 10; Data.CollisionShapes[0].LevelSet.MinClusterLevelSetResolution = 25; Data.CollisionShapes[0].LevelSet.MaxClusterLevelSetResolution = 50; Data.CollisionShapes[0].CollisionObjectReductionPercentage = 1.0; Data.CollisionShapes[0].CollisionMarginFraction = UPhysicsSettings::Get()->SolverOptions.CollisionMarginFraction; Data.CollisionShapes[0].CollisionParticles.CollisionParticlesFraction = 1.0; Data.CollisionShapes[0].CollisionParticles.MaximumCollisionParticles = 60; } Data.DamageThreshold = 5000.0f; return Data; } void UGeometryCollection::ValidateSizeSpecificDataDefaults() { auto HasDefault = [](const TArray& DatasIn) { for (const FGeometryCollectionSizeSpecificData& Data : DatasIn) { if (Data.MaxSize >= DefaultMaxSizeValue) { return true; } } return false; }; if (!SizeSpecificData.Num() || !HasDefault(SizeSpecificData)) { FGeometryCollectionSizeSpecificData Data = GeometryCollectionSizeSpecificDataDefaults(); if (Data.CollisionShapes.Num()) { #if WITH_EDITORONLY_DATA Data.CollisionShapes[0].CollisionType = CollisionType_DEPRECATED; Data.CollisionShapes[0].ImplicitType = ImplicitType_DEPRECATED; Data.CollisionShapes[0].LevelSet.MinLevelSetResolution = MinLevelSetResolution_DEPRECATED; Data.CollisionShapes[0].LevelSet.MaxLevelSetResolution = MaxLevelSetResolution_DEPRECATED; Data.CollisionShapes[0].LevelSet.MinClusterLevelSetResolution = MinClusterLevelSetResolution_DEPRECATED; Data.CollisionShapes[0].LevelSet.MaxClusterLevelSetResolution = MaxClusterLevelSetResolution_DEPRECATED; Data.CollisionShapes[0].CollisionObjectReductionPercentage = CollisionObjectReductionPercentage_DEPRECATED; Data.CollisionShapes[0].CollisionMarginFraction = UPhysicsSettings::Get()->SolverOptions.CollisionMarginFraction; #endif if (Data.CollisionShapes[0].ImplicitType == EImplicitTypeEnum::Chaos_Implicit_LevelSet) { Data.CollisionShapes[0].CollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; } } SizeSpecificData.Add(Data); } check(SizeSpecificData.Num()); } // update cachedroot index using the current hierarchy setup void UGeometryCollection::UpdateRootIndex() { RootIndex = INDEX_NONE; if (GeometryCollection) { Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*GeometryCollection); RootIndex = HierarchyFacade.GetRootIndex(); } } void UGeometryCollection::CacheBreadthFirstTransformIndices() { BreadthFirstTransformIndices.Reset(); if (GeometryCollection) { Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*GeometryCollection); BreadthFirstTransformIndices = HierarchyFacade.ComputeTransformIndicesInBreadthFirstOrder(); } } void UGeometryCollection::CacheAutoInstanceTransformRemapIndices() { AutoInstanceTransformRemapIndices.Reset(); if (GeometryCollection == nullptr) { return; } const GeometryCollection::Facades::FCollectionInstancedMeshFacade InstancedMeshFacade(*GeometryCollection); if (!InstancedMeshFacade.IsValid()) { return; } const int32 NumMeshes = AutoInstanceMeshes.Num(); if(NumMeshes!=0) { TArray TransformGroups; TransformGroups.AddZeroed(NumMeshes); TArray TransformStarts; TransformStarts.AddUninitialized(NumMeshes); TArray InstanceCounts; InstanceCounts.AddUninitialized(NumMeshes); TArray WrittenTransformCounts; WrittenTransformCounts.AddZeroed(NumMeshes); for (int32 MeshIndex = 0; MeshIndex < NumMeshes; MeshIndex++) { const int32 NumInstances = AutoInstanceMeshes[MeshIndex].NumInstances; TransformStarts[MeshIndex] = MeshIndex == 0 ? 0 : TransformStarts[MeshIndex - 1] + InstanceCounts[MeshIndex - 1]; InstanceCounts[MeshIndex] = NumInstances; } AutoInstanceTransformRemapIndices.AddUninitialized(TransformStarts.Last() + InstanceCounts.Last()); const int32 NumTransforms = InstancedMeshFacade.GetNumIndices(); for (int32 TransformIndex = 0; TransformIndex < NumTransforms; TransformIndex++) { if (GeometryCollection->Children[TransformIndex].Num() == 0) { const int32 AutoInstanceMeshIndex = InstancedMeshFacade.GetIndex(TransformIndex); const int32 WriteIndex = WrittenTransformCounts[AutoInstanceMeshIndex]; if (WriteIndex < InstanceCounts[AutoInstanceMeshIndex]) { const int32 TransformArrayIndex = TransformStarts[AutoInstanceMeshIndex] + WriteIndex; if(AutoInstanceTransformRemapIndices.IsValidIndex(TransformArrayIndex)) { AutoInstanceTransformRemapIndices[TransformArrayIndex] = TransformIndex; WrittenTransformCounts[AutoInstanceMeshIndex]++; } } } } } } void UGeometryCollection::UpdateGeometryDependentProperties() { #if WITH_EDITOR // Note: Currently, computing convex hulls also always computes proximity (if missing) as well as volumes and size. // If adding a condition where we do not compute convex hulls, make sure to still compute proximity, volumes and size here UpdateConvexGeometry(); #endif } void UGeometryCollection::UpdateConvexGeometryIfMissing() { const bool bConvexAttributeMissing = !GeometryCollection->HasAttribute(FGeometryCollection::ConvexHullAttribute, FGeometryCollection::ConvexGroup); if (GeometryCollection && bConvexAttributeMissing) { UpdateConvexGeometry(); } } void UGeometryCollection::UpdateConvexGeometry() { #if WITH_EDITOR if (GeometryCollection) { FGeometryCollectionConvexPropertiesInterface::FConvexCreationProperties ConvexProperties = GeometryCollection->GetConvexProperties(); FGeometryCollectionConvexUtility::CreateNonOverlappingConvexHullData(GeometryCollection.Get(), ConvexProperties.FractionRemove, ConvexProperties.SimplificationThreshold, ConvexProperties.CanExceedFraction, ConvexProperties.RemoveOverlaps, ConvexProperties.OverlapRemovalShrinkPercent); InvalidateCollection(); } #endif } void UGeometryCollection::PostInitProperties() { #if WITH_EDITORONLY_DATA if (!HasAnyFlags(RF_ClassDefaultObject)) { AssetImportData = NewObject(this, TEXT("AssetImportData")); } #endif Super::PostInitProperties(); } void UGeometryCollection::CacheMaterialDensity() { CachedDensityFromPhysicsMaterialInGCm3 = 0; UPhysicalMaterial* PhysicsMaterialForDensity = PhysicsMaterial; if (!PhysicsMaterialForDensity) { PhysicsMaterialForDensity = GEngine ? GEngine->DefaultPhysMaterial : nullptr; } if (PhysicsMaterialForDensity) { CachedDensityFromPhysicsMaterialInGCm3 = PhysicsMaterialForDensity->Density; } } float UGeometryCollection::GetMassOrDensity(bool& bOutIsDensity) const { return GetMassOrDensityInternal(bOutIsDensity, /* bCached */ true); } float UGeometryCollection::GetMassOrDensityInternal(bool& bOutIsDensity, bool bCached) const { bOutIsDensity = bMassAsDensity; float MassOrDensity = bMassAsDensity ? Chaos::KgM3ToKgCm3(Mass) : Mass; if (bDensityFromPhysicsMaterial) { if (bCached && CachedDensityFromPhysicsMaterialInGCm3 > 0) { bOutIsDensity = true; MassOrDensity = Chaos::GCm3ToKgCm3(CachedDensityFromPhysicsMaterialInGCm3); } else { UPhysicalMaterial* PhysicsMaterialForDensity = PhysicsMaterial; if (!PhysicsMaterialForDensity) { PhysicsMaterialForDensity = GEngine ? GEngine->DefaultPhysMaterial : nullptr; } if (ensureMsgf(PhysicsMaterialForDensity, TEXT("bDensityFromPhysicsMaterial is true but no physics material has been set (and engine default cannot be found )"))) { // materials only provide density bOutIsDensity = true; MassOrDensity = Chaos::GCm3ToKgCm3(PhysicsMaterialForDensity->Density); } } } return MassOrDensity; } void UGeometryCollection::GetSharedSimulationParams(FSharedSimulationParameters& OutParams) const { const FGeometryCollectionSizeSpecificData& SizeSpecificDefault = GetDefaultSizeSpecificData(); // we grab the non cached version because this is going to be used to generate the mass attribute which will eventually cache the density value if necessary bool bUseMassAsDensity = false; OutParams.Mass = GetMassOrDensityInternal(bUseMassAsDensity, false); OutParams.bMassAsDensity = bUseMassAsDensity; OutParams.MinimumMassClamp = MinimumMassClamp; FGeometryCollectionSizeSpecificData InfSize; if (SizeSpecificDefault.CollisionShapes.Num()) { InfSize.CollisionShapes.SetNum(1); // @todo(chaos destruction collisions) : Add support for multiple shapes. OutParams.MaximumCollisionParticleCount = SizeSpecificDefault.CollisionShapes[0].CollisionParticles.MaximumCollisionParticles; ECollisionTypeEnum SelectedCollisionType = SizeSpecificDefault.CollisionShapes[0].CollisionType; if (SelectedCollisionType == ECollisionTypeEnum::Chaos_Volumetric && SizeSpecificDefault.CollisionShapes[0].ImplicitType == EImplicitTypeEnum::Chaos_Implicit_LevelSet) { UE_LOG(LogGeometryCollectionInternal, Verbose, TEXT("LevelSet geometry selected but non-particle collisions selected. Forcing particle-implicit collisions for %s"), *GetPathName()); SelectedCollisionType = ECollisionTypeEnum::Chaos_Surface_Volumetric; } InfSize.CollisionShapes[0].CollisionType = SelectedCollisionType; InfSize.CollisionShapes[0].ImplicitType = SizeSpecificDefault.CollisionShapes[0].ImplicitType; InfSize.CollisionShapes[0].LevelSet.MinLevelSetResolution = SizeSpecificDefault.CollisionShapes[0].LevelSet.MinLevelSetResolution; InfSize.CollisionShapes[0].LevelSet.MaxLevelSetResolution = SizeSpecificDefault.CollisionShapes[0].LevelSet.MaxLevelSetResolution; InfSize.CollisionShapes[0].LevelSet.MinClusterLevelSetResolution = SizeSpecificDefault.CollisionShapes[0].LevelSet.MinClusterLevelSetResolution; InfSize.CollisionShapes[0].LevelSet.MaxClusterLevelSetResolution = SizeSpecificDefault.CollisionShapes[0].LevelSet.MaxClusterLevelSetResolution; InfSize.CollisionShapes[0].CollisionObjectReductionPercentage = SizeSpecificDefault.CollisionShapes[0].CollisionObjectReductionPercentage; InfSize.CollisionShapes[0].CollisionMarginFraction = SizeSpecificDefault.CollisionShapes[0].CollisionMarginFraction; InfSize.CollisionShapes[0].CollisionParticles.CollisionParticlesFraction = SizeSpecificDefault.CollisionShapes[0].CollisionParticles.CollisionParticlesFraction; InfSize.CollisionShapes[0].CollisionParticles.MaximumCollisionParticles = SizeSpecificDefault.CollisionShapes[0].CollisionParticles.MaximumCollisionParticles; } InfSize.MaxSize = TNumericLimits::Max(); OutParams.SizeSpecificData.SetNum(SizeSpecificData.Num() + 1); FillSharedSimulationSizeSpecificData(OutParams.SizeSpecificData[0], InfSize); for (int32 Idx = 0; Idx < SizeSpecificData.Num(); ++Idx) { FillSharedSimulationSizeSpecificData(OutParams.SizeSpecificData[Idx+1], SizeSpecificData[Idx]); } OutParams.bUseImportedCollisionImplicits = bImportCollisionFromSource; OutParams.SizeSpecificData.Sort(); //can we do this at editor time on post edit change? } bool UGeometryCollection::IsEmpty() const { return (NumElements(FGeometryCollection::TransformGroup) == 0); } void UGeometryCollection::Reset() { if (GeometryCollection.IsValid()) { Modify(); GeometryCollection->Reset(); Materials.Empty(); EmbeddedGeometryExemplar.Empty(); AutoInstanceMeshes.Empty(); InvalidateCollection(); } } namespace UE::Dataflow::Private { static void SetRandomBoneColor(TSharedPtr& InGeometryCollection) { TManagedArray& BoneColors = InGeometryCollection->BoneColor; const int32 NumBones = BoneColors.Num(); FRandomStream RandomStream(NumBones); for (int32 Idx = 0; Idx < NumBones; ++Idx) { const uint8 R = static_cast(RandomStream.FRandRange(5, 105)); const uint8 G = static_cast(RandomStream.FRandRange(5, 105)); const uint8 B = static_cast(RandomStream.FRandRange(5, 105)); BoneColors[Idx] = FLinearColor(FColor(R, G, B, 255)); } } } void UGeometryCollection::ResetFrom(const FManagedArrayCollection& InCollection, const TArray& InMaterials, bool bHasInternalMaterials) { Reset(); if (GeometryCollection.IsValid()) { InCollection.CopyTo(GeometryCollection.Get()); // todo(Chaos) : we could certainly run a "dependent attribute update method here instead of having to known about convex specifically UpdateConvexGeometryIfMissing(); Materials.Append(InMaterials); InitializeMaterials(bHasInternalMaterials); // Randomize BoneColor UE::Dataflow::Private::SetRandomBoneColor(GeometryCollection); } } void UGeometryCollection::ResetFrom(const FManagedArrayCollection& InCollection, const TArray& InMaterialInstances, bool bHasInternalMaterials) { Reset(); if (GeometryCollection.IsValid()) { InCollection.CopyTo(GeometryCollection.Get()); // todo(Chaos) : we could certainly run a "dependent attribute update method here instead of having to known about convex specifically UpdateConvexGeometryIfMissing(); Materials.Append(InMaterialInstances); InitializeMaterials(bHasInternalMaterials); // Randomize BoneColor UE::Dataflow::Private::SetRandomBoneColor(GeometryCollection); } } /** AppendGeometry */ int32 UGeometryCollection::AppendGeometry(const UGeometryCollection & Element, bool ReindexAllMaterials, const FTransform& TransformRoot) { Modify(); InvalidateCollection(); // add all materials // if there are none, we assume all material assignments in Element are shared by this GeometryCollection // otherwise, we assume all assignments come from the contained materials int32 MaterialIDOffset = 0; if (Element.Materials.Num() > 0) { MaterialIDOffset = Materials.Num(); Materials.Append(Element.Materials); } return GeometryCollection->AppendGeometry(*Element.GetGeometryCollection(), MaterialIDOffset, ReindexAllMaterials, TransformRoot); } /** NumElements */ int32 UGeometryCollection::NumElements(const FName& Group) const { return GeometryCollection? GeometryCollection->NumElements(Group): 0; } /** RemoveElements */ void UGeometryCollection::RemoveElements(const FName & Group, const TArray& SortedDeletionList) { Modify(); GeometryCollection->RemoveElements(Group, SortedDeletionList); InvalidateCollection(); } int UGeometryCollection::GetDefaultSizeSpecificDataIndex() const { int LargestIndex = INDEX_NONE; float MaxSize = TNumericLimits::Lowest(); for (int i = 0; i < SizeSpecificData.Num(); i++) { const float SizeSpecificDataMaxSize = SizeSpecificData[i].MaxSize; if (MaxSize < SizeSpecificDataMaxSize) { MaxSize = SizeSpecificDataMaxSize; LargestIndex = i; } } check(LargestIndex != INDEX_NONE && LargestIndex < SizeSpecificData.Num()); return LargestIndex; } /** Size Specific Data Access */ FGeometryCollectionSizeSpecificData& UGeometryCollection::GetDefaultSizeSpecificData() { if (!SizeSpecificData.Num()) { SizeSpecificData.Add(GeometryCollectionSizeSpecificDataDefaults()); } const int DefaultSizeIndex = GetDefaultSizeSpecificDataIndex(); return SizeSpecificData[DefaultSizeIndex]; } const FGeometryCollectionSizeSpecificData& UGeometryCollection::GetDefaultSizeSpecificData() const { ensure(SizeSpecificData.Num()); const int DefaultSizeIndex = GetDefaultSizeSpecificDataIndex(); return SizeSpecificData[DefaultSizeIndex]; } /** ReindexMaterialSections */ void UGeometryCollection::ReindexMaterialSections() { Modify(); GeometryCollection->ReindexMaterials(); InvalidateCollection(); } UMaterialInterface* UGeometryCollection::GetBoneSelectedMaterial() { #if WITH_EDITORONLY_DATA return LoadObject(nullptr, GetSelectedMaterialPath(), nullptr, LOAD_None, nullptr); #else return nullptr; #endif } void UGeometryCollection::InitializeMaterials(bool bHasLegacyInternalMaterialsPairs) { Modify(); if (Materials.IsEmpty()) { Materials.Add({}); // add an empty material is none exist } // Initialize the BoneSelectedMaterial separate from the materials on the collection UMaterialInterface* BoneSelectedMaterial = GetBoneSelectedMaterial(); TManagedArray& MaterialIDs = GeometryCollection->MaterialID; // normally we filter out instances of the selection material ID, but if it's actually used on any face we have to keep it bool bBoneSelectedMaterialIsUsed = false; for (int32 FaceIdx = 0; FaceIdx < MaterialIDs.Num(); ++FaceIdx) { int32 FaceMaterialID = MaterialIDs[FaceIdx]; if (FaceMaterialID < Materials.Num() && Materials[FaceMaterialID] == BoneSelectedMaterial) { bBoneSelectedMaterialIsUsed = true; break; } } TArray FinalMaterials; if (bHasLegacyInternalMaterialsPairs) { // We're assuming that all materials are arranged in pairs, so first we collect these. using FMaterialPair = TPair; TSet MaterialSet; for (int32 MaterialIndex = 0; MaterialIndex < Materials.Num(); ++MaterialIndex) { UMaterialInterface* ExteriorMaterial = Materials[MaterialIndex]; if (ExteriorMaterial == BoneSelectedMaterial && !bBoneSelectedMaterialIsUsed) // skip unused bone selected material { continue; } // If we have an odd number of materials, the last material duplicates itself. UMaterialInterface* InteriorMaterial = Materials[MaterialIndex]; while (++MaterialIndex < Materials.Num()) { if (Materials[MaterialIndex] == BoneSelectedMaterial && !bBoneSelectedMaterialIsUsed) // skip bone selected material { continue; } InteriorMaterial = Materials[MaterialIndex]; break; } MaterialSet.Add(FMaterialPair(ExteriorMaterial, InteriorMaterial)); } // create the final material array only containing unique materials // alternating exterior and interior materials TMap ExteriorMaterialPtrToArrayIndex; TMap InteriorMaterialPtrToArrayIndex; for (const FMaterialPair& Curr : MaterialSet) { // Add base material TTuple< UMaterialInterface*, int32> BaseTuple(Curr.Key, FinalMaterials.Add(Curr.Key)); ExteriorMaterialPtrToArrayIndex.Add(BaseTuple); // Add interior material TTuple< UMaterialInterface*, int32> InteriorTuple(Curr.Value, FinalMaterials.Add(Curr.Value)); InteriorMaterialPtrToArrayIndex.Add(InteriorTuple); } // Reassign material ID for each face given the new consolidated array of materials for (int32 Material = 0; Material < MaterialIDs.Num(); ++Material) { if (MaterialIDs[Material] < Materials.Num()) { UMaterialInterface* OldMaterialPtr = Materials[MaterialIDs[Material]]; if (MaterialIDs[Material] % 2 == 0) { MaterialIDs[Material] = *ExteriorMaterialPtrToArrayIndex.Find(OldMaterialPtr); } else { MaterialIDs[Material] = *InteriorMaterialPtrToArrayIndex.Find(OldMaterialPtr); } } } } else { // simple deduping process for (int32 MaterialIndex = 0; MaterialIndex < Materials.Num(); ++MaterialIndex) { UMaterialInterface* Material = Materials[MaterialIndex]; if (Material == BoneSelectedMaterial && !bBoneSelectedMaterialIsUsed) // skip unused bone selected material { continue; } FinalMaterials.AddUnique(Material); } // Reassign material ID for each face given the new consolidated array of materials for (int32 MaterialIDIndex = 0; MaterialIDIndex < MaterialIDs.Num(); MaterialIDIndex++) { const int32 OldMaterialID = MaterialIDs[MaterialIDIndex]; if (Materials.IsValidIndex(OldMaterialID)) { UMaterialInterface* Material = Materials[OldMaterialID]; MaterialIDs[MaterialIDIndex] = FinalMaterials.Find(Material); } } } // Set new material array on the collection Materials = FinalMaterials; // BoneSelectedMaterial is no longer stored in the general Materials array BoneSelectedMaterialIndex = INDEX_NONE; GeometryCollection->ReindexMaterials(); InvalidateCollection(); } int32 UGeometryCollection::AddNewMaterialSlot(bool bCopyLastMaterial) { Modify(); int32 NewIdx = Materials.Emplace(); if (NewIdx > 0 && bCopyLastMaterial) { Materials[NewIdx] = Materials[NewIdx - 1]; } InvalidateCollection(); return NewIdx; } bool UGeometryCollection::RemoveLastMaterialSlot() { if (Materials.Num() > 1) { Modify(); Materials.Pop(); InvalidateCollection(); return true; } return false; } /** Returns true if there is anything to render */ bool UGeometryCollection::HasVisibleGeometry() const { if(ensureMsgf(GeometryCollection.IsValid(), TEXT("Geometry Collection has an invalid internal collection"))) { return ( (EnableNanite && RenderData && RenderData->bHasNaniteData) || GeometryCollection->HasVisibleGeometry()); } return false; } struct FPackedHierarchyNode_Old { FSphere LODBounds[64]; FSphere Bounds[64]; struct { uint32 MinLODError_MaxParentLODError; uint32 ChildStartReference; uint32 ResourcePageIndex_NumPages_GroupPartSize; } Misc[64]; }; FArchive& operator<<(FArchive& Ar, FPackedHierarchyNode_Old& Node) { for (uint32 i = 0; i < 64; i++) { Ar << Node.LODBounds[i]; Ar << Node.Bounds[i]; Ar << Node.Misc[i].MinLODError_MaxParentLODError; Ar << Node.Misc[i].ChildStartReference; Ar << Node.Misc[i].ResourcePageIndex_NumPages_GroupPartSize; } return Ar; } struct FPageStreamingState_Old { uint32 BulkOffset; uint32 BulkSize; uint32 PageUncompressedSize; uint32 DependenciesStart; uint32 DependenciesNum; }; FArchive& operator<<(FArchive& Ar, FPageStreamingState_Old& PageStreamingState) { Ar << PageStreamingState.BulkOffset; Ar << PageStreamingState.BulkSize; Ar << PageStreamingState.PageUncompressedSize; Ar << PageStreamingState.DependenciesStart; Ar << PageStreamingState.DependenciesNum; return Ar; } // Parse old Nanite data and throw it away. We need this to not crash when parsing old files. static void SerializeOldNaniteData(FArchive& Ar, UGeometryCollection* Owner) { check(Ar.IsLoading()); int32 NumNaniteResources = 0; Ar << NumNaniteResources; for (int32 i = 0; i < NumNaniteResources; ++i) { FStripDataFlags StripFlags(Ar, 0); if (!StripFlags.IsAudioVisualDataStripped()) { bool bLZCompressed; TArray< uint8 > RootClusterPage; FByteBulkData StreamableClusterPages; TArray< uint16 > ImposterAtlas; TArray< FPackedHierarchyNode_Old > HierarchyNodes; TArray< FPageStreamingState_Old > PageStreamingStates; TArray< uint32 > PageDependencies; Ar << bLZCompressed; Ar << RootClusterPage; StreamableClusterPages.Serialize(Ar, Owner, 0); Ar << PageStreamingStates; Ar << HierarchyNodes; Ar << PageDependencies; Ar << ImposterAtlas; } } } /** Serialize */ void UGeometryCollection::Serialize(FArchive& Ar) { bool bCreateSimulationData = false; Ar.UsingCustomVersion(FDestructionObjectVersion::GUID); Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID); Ar.UsingCustomVersion(FUE5ReleaseStreamObjectVersion::GUID); Ar.UsingCustomVersion(FPhysicsObjectVersion::GUID); Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); Chaos::FChaosArchive ChaosAr(Ar); // The Geometry Collection we will be archiving. This may be replaced with a transient, stripped back Geometry Collection if we are cooking. TSharedPtr ArchiveGeometryCollection = GeometryCollection; TObjectPtr StrippedDataflowAsset = nullptr; FDataflowInstance StrippedDataflowInstance; bool bStrippedDataflowData = false; bool bIsCookedOrCooking = Ar.IsCooking(); if ((bIsCookedOrCooking && Ar.IsSaving()) || (Ar.IsCountingMemory() && Ar.IsFilterEditorOnly())) { #if WITH_EDITOR if (bStripOnCook) { // TODO: Since non-nanite path now stores mesh data in cooked build we may be able to unify // the simplification of the Geometry Collection for both nanite and non-nanite cases. if (EnableNanite && HasNaniteData()) { // If this is a cooked archive, we strip unnecessary data from the Geometry Collection to keep the memory footprint as small as possible. ArchiveGeometryCollection = GenerateMinimalGeometryCollection(); } else { // non-nanite path where it may be necessary to remove geometry if the geometry collection is rendered using ISMPool or an external rendering system ArchiveGeometryCollection = CopyCollectionAndRemoveGeometry(GeometryCollection); } } else { // do we need to remove the simplicial attribute ? if (false == FGeometryCollection::AreCollisionParticlesEnabled()) { ArchiveGeometryCollection = TSharedPtr(new FGeometryCollection); const TArray NoGroupsToSkip; const TArray> AttributesToSkip{ { FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup } }; GeometryCollection->CopyTo(ArchiveGeometryCollection.Get(), NoGroupsToSkip, AttributesToSkip); } } // The dataflow asset is only needed for the editor, so we just remove it when cooking StrippedDataflowAsset = DataflowAsset; DataflowAsset = nullptr; StrippedDataflowInstance = DataflowInstance; DataflowInstance = FDataflowInstance(); bStrippedDataflowData = true; #endif } #if WITH_EDITOR //Early versions did not have tagged properties serialize first if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::GeometryCollectionInDDC) { if (Ar.IsLoading()) { GeometryCollection->Serialize(ChaosAr); } else { ArchiveGeometryCollection->Serialize(ChaosAr); } } if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::AddedTimestampedGeometryComponentCache) { if (Ar.IsLoading()) { // Strip old recorded cache data int32 DummyNumFrames; TArray> DummyTransforms; Ar << DummyNumFrames; DummyTransforms.SetNum(DummyNumFrames); for (int32 Index = 0; Index < DummyNumFrames; ++Index) { Ar << DummyTransforms[Index]; } } } else #endif { // Push up the chain to hit tagged properties too // This should have always been in here but because we have saved assets // from before this line was here it has to be gated Super::Serialize(Ar); } // Important : this needs to remain after the call to Super::Serialize if (bStrippedDataflowData) { #if WITH_EDITORONLY_DATA DataflowAsset = StrippedDataflowAsset; #endif DataflowInstance = StrippedDataflowInstance; } if ((Ar.IsLoading() || Ar.IsSaving()) && !SizeSpecificData.Num()) { // Validation is necessary when loading old version and when saving newly created version // that might not have created the defaults yet; the defaults are used during EnsureDataIsCooked. ValidateSizeSpecificDataDefaults(); } if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::DensityUnitsChanged) { if (bMassAsDensity) { Mass = Chaos::KgCm3ToKgM3(Mass); } } if (Ar.CustomVer(FDestructionObjectVersion::GUID) >= FDestructionObjectVersion::GeometryCollectionInDDC) { Ar << bIsCookedOrCooking; } //new versions serialize geometry collection after tagged properties if (Ar.CustomVer(FDestructionObjectVersion::GUID) >= FDestructionObjectVersion::GeometryCollectionInDDCAndAsset) { #if WITH_EDITOR if (Ar.IsSaving() && !Ar.IsTransacting()) { constexpr bool bAllowCopyFromDDC = false; constexpr bool bIsTransacting = false; // the surrounding if statement garantees that EnsureSimulationDataIsCooked(bIsTransacting, bAllowCopyFromDDC); } #endif if (Ar.IsLoading() || (Ar.IsCountingMemory() && !Ar.IsFilterEditorOnly())) { GeometryCollection->Serialize(ChaosAr); } else { ArchiveGeometryCollection->Serialize(ChaosAr); } TManagedArray* NewAttr = ArchiveGeometryCollection->FindAttributeTyped( FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); if(!NewAttr && Ar.IsLoading()) { const int32 NumElems = GeometryCollection->NumElements(FTransformCollection::TransformGroup); TArray ImplicitObjects; ImplicitObjects.SetNum(NumElems); const TManagedArray>* OldAttrA = ArchiveGeometryCollection->FindAttributeTyped>(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); const TManagedArray>* OldAttrB = ArchiveGeometryCollection->FindAttributeTyped>(FGeometryDynamicCollection::SharedImplicitsAttribute, FTransformCollection::TransformGroup); const TManagedArray>* OldAttrC = ArchiveGeometryCollection->FindAttributeTyped>(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); // Some geometry collection can still store several of those arrays // We need to make sure to remove all of them and keep the last good one if (OldAttrA) { for (int32 Index = 0; Index < NumElems; ++Index) { if( (*OldAttrA)[Index] != nullptr) { ImplicitObjects[Index] = Chaos::FImplicitObjectPtr((*OldAttrA)[Index]->DeepCopyGeometry()); }; } ArchiveGeometryCollection->RemoveAttribute(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); } if (OldAttrB) { for (int32 Index = 0; Index < NumElems; ++Index) { if( (*OldAttrB)[Index] != nullptr) { ImplicitObjects[Index] = Chaos::FImplicitObjectPtr((*OldAttrB)[Index]->DeepCopyGeometry()); } } ArchiveGeometryCollection->RemoveAttribute(FGeometryDynamicCollection::SharedImplicitsAttribute, FTransformCollection::TransformGroup); } if (OldAttrC) { for (int32 Index = 0; Index < NumElems; ++Index) { if( (*OldAttrC)[Index] != nullptr) { ImplicitObjects[Index] = Chaos::FImplicitObjectPtr((*OldAttrC)[Index]->DeepCopyGeometry()); } } ArchiveGeometryCollection->RemoveAttribute(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); } NewAttr = &ArchiveGeometryCollection->AddAttribute(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); for (int32 Index = 0; Index < NumElems; ++Index) { (*NewAttr)[Index] = ImplicitObjects[Index]; } } } if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::GroupAndAttributeNameRemapping) { ArchiveGeometryCollection->UpdateOldAttributeNames(); InvalidateCollection(); bCreateSimulationData = true; } if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) == FUE5MainStreamObjectVersion::GeometryCollectionNaniteData || (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) >= FUE5MainStreamObjectVersion::GeometryCollectionNaniteCooked && Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::GeometryCollectionNaniteTransient)) { // This legacy version serialized structure information into archive, but the data is transient. // Just load it and throw away here, it will be rebuilt later and resaved past this point. SerializeOldNaniteData(ChaosAr, this); } if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) >= FUE5MainStreamObjectVersion::GeometryCollectionNaniteTransient) { bool bCooked = Ar.IsCooking(); Ar << bCooked; if (bCooked) { if (RenderData == nullptr) { RenderData = MakeUnique(); } RenderData->Serialize(ChaosAr, *this); } } { TManagedArray* NewAttr = ArchiveGeometryCollection->FindAttributeTyped( FTransformCollection::ConvexHullAttribute, FTransformCollection::ConvexGroup); if(!NewAttr && Ar.IsLoading()) { const int32 NumElems = GeometryCollection->NumElements(FTransformCollection::ConvexGroup); TArray ImplicitObjects; ImplicitObjects.SetNum(NumElems); if( TManagedArray>* OldAttr = ArchiveGeometryCollection->FindAttributeTyped>( FTransformCollection::ConvexHullAttribute, FTransformCollection::ConvexGroup)) { for (int32 Index = 0; Index < NumElems; ++Index) { if((*OldAttr)[Index] != nullptr) { ImplicitObjects[Index] = Chaos::FConvexPtr((*OldAttr)[Index].Release()); } } ArchiveGeometryCollection->RemoveAttribute(FTransformCollection::ConvexHullAttribute, FTransformCollection::ConvexGroup); } NewAttr = &ArchiveGeometryCollection->AddAttribute(FTransformCollection::ConvexHullAttribute, FTransformCollection::ConvexGroup); for (int32 Index = 0; Index < NumElems; ++Index) { (*NewAttr)[Index] = ImplicitObjects[Index]; } } } { TManagedArray* NewAttr = ArchiveGeometryCollection->FindAttributeTyped( FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup); if(!NewAttr && Ar.IsLoading()) { const int32 NumElems = GeometryCollection->NumElements(FGeometryCollection::TransformGroup); TArray ImplicitObjects; ImplicitObjects.SetNum(NumElems); if( TManagedArray>* OldAttr = ArchiveGeometryCollection->FindAttributeTyped>( FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup)) { for (int32 Index = 0; Index < NumElems; ++Index) { if((*OldAttr)[Index] != nullptr) { ImplicitObjects[Index] = Chaos::FImplicitObjectPtr((*OldAttr)[Index]->DeepCopyGeometry()); } } ArchiveGeometryCollection->RemoveAttribute(FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup); } NewAttr = &ArchiveGeometryCollection->AddAttribute(FGeometryCollection::ExternalCollisionsAttribute, FGeometryCollection::TransformGroup); for (int32 Index = 0; Index < NumElems; ++Index) { (*NewAttr)[Index] = ImplicitObjects[Index]; } } } // will generate convex bodies when they dont exist. if (Ar.CustomVer(FUE5ReleaseStreamObjectVersion::GUID) < FUE5ReleaseStreamObjectVersion::GeometryCollectionConvexDefaults && Ar.CustomVer(FPhysicsObjectVersion::GUID) < FPhysicsObjectVersion::GeometryCollectionConvexDefaults) { #if WITH_EDITOR if (bGeometryCollectionEnableForcedConvexGenerationInSerialize) { if (!FGeometryCollectionConvexUtility::HasConvexHullData(GeometryCollection.Get()) && GeometryCollection::SizeSpecific::UsesImplicitCollisionType(SizeSpecificData, EImplicitTypeEnum::Chaos_Implicit_Convex)) { GeometryCollection::SizeSpecific::SetImplicitCollisionType(SizeSpecificData, EImplicitTypeEnum::Chaos_Implicit_Box, EImplicitTypeEnum::Chaos_Implicit_Convex); bCreateSimulationData = true; InvalidateCollection(); } } #endif } if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::GeometryCollectionPerChildDamageThreshold) { // prior this version, damage threshold were computed per cluster and propagated to children PerClusterOnlyDamageThreshold = true; } if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::GeometryCollectionDamagePropagationData) { // prior this version, damage propagation was not enabled by default DamagePropagationData.bEnabled = false; } if (Ar.IsLoading() && !bIsCookedOrCooking && BoneSelectedMaterialIndex != INDEX_NONE) { if (Materials.IsValidIndex(BoneSelectedMaterialIndex)) { // Remove the material assuming it's the last in the list (otherwise, leave it, as it's not clear why it would be in that state) if (BoneSelectedMaterialIndex == Materials.Num() - 1) { Materials.RemoveAt(BoneSelectedMaterialIndex); } } BoneSelectedMaterialIndex = INDEX_NONE; } // Make sure the root index is properly set if (RootIndex == INDEX_NONE) { UpdateRootIndex(); } // Generate root to leave order lookup CacheBreadthFirstTransformIndices(); // Generate transform remap for AutoInstanceMeshes instances CacheAutoInstanceTransformRemapIndices(); if (Ar.IsLoading()) { FillAutoInstanceMeshesInstancesIfNeeded(); } #if WITH_EDITORONLY_DATA if (bCreateSimulationData) { CreateSimulationData(); } //for all versions loaded, make sure loaded content is built if (Ar.IsLoading()) { // note: don't allow copy from DDC here, since we've already loaded the data above, and the DDC data does not include any data migrations performed by the load constexpr bool bAllowCopyFromDDC = false; EnsureSimulationDataIsCooked(Ar.IsTransacting(), bAllowCopyFromDDC); } #endif if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::GeometryCollectionConvertVertexColorToSRGB) { // disable sRGB conversion for old assets to keep the default behavior from before this setting existed // (new assets will default-enable the conversion, because that matches static meshes and is the more-expected behavior) bConvertVertexColorsToSRGB = false; } } const TCHAR* UGeometryCollection::GetSelectedMaterialPath() { return TEXT("/Engine/EditorMaterials/GeometryCollection/SelectedGeometryMaterial.SelectedGeometryMaterial"); } void UGeometryCollection::SetEnableNanite(bool bValue) { if (EnableNanite != bValue) { EnableNanite = bValue; #if WITH_EDITOR RebuildRenderData(); #endif } } void UGeometryCollection::SetConvertVertexColorsToSRGB(bool bValue) { if (bConvertVertexColorsToSRGB != bValue) { bConvertVertexColorsToSRGB = bValue; #if WITH_EDITOR RebuildRenderData(); #endif } } void UGeometryCollection::FillAutoInstanceMeshesInstancesIfNeeded() { // make sure the instanced meshes have their instance count properly set if (GeometryCollection && AutoInstanceMeshes.Num() > 0 && AutoInstanceMeshes[0].NumInstances == 0) { // make sure to rest all of it first for (FGeometryCollectionAutoInstanceMesh& AutoInstanceMesh : AutoInstanceMeshes) { AutoInstanceMesh.NumInstances = 0; } const GeometryCollection::Facades::FCollectionInstancedMeshFacade InstancedMeshFacade(*GeometryCollection); if (InstancedMeshFacade.IsValid()) { const int32 NumTransforms = GeometryCollection->Children.Num(); for (int32 TransformIndex = 0; TransformIndex < NumTransforms; TransformIndex++) { // only applies to leaves nodes if (GeometryCollection->Children[TransformIndex].Num() == 0) { const int32 AutoInstanceMeshIndex = InstancedMeshFacade.GetIndex(TransformIndex); if (AutoInstanceMeshes.IsValidIndex(AutoInstanceMeshIndex)) { AutoInstanceMeshes[AutoInstanceMeshIndex].NumInstances++; } } } } else { UE_LOG(LogGeometryCollectionInternal, Warning, TEXT("[%s] Could not find AutoInstanceMeshIndex attribute but the asset as instanced meshes assigned, you may need to regenerate this asset"), *GetPathName()); } } } #if WITH_EDITOR void UGeometryCollection::CreateSimulationDataImp(bool bCopyFromDDC) { COOK_STAT(auto Timer = GeometryCollectionCookStats::UsageStats.TimeSyncWork()); // Skips the DDC fetch entirely for testing the builder without adding to the DDC const static bool bSkipDDC = false; //Use the DDC to build simulation data. If we are loading in the editor we then serialize this data into the geometry collection TArray DDCData; FDerivedDataGeometryCollectionCooker* GeometryCollectionCooker = new FDerivedDataGeometryCollectionCooker(*this); if (GeometryCollectionCooker->CanBuild()) { if (bSkipDDC) { GeometryCollectionCooker->Build(DDCData); COOK_STAT(Timer.AddMiss(DDCData.Num())); } else { bool bBuilt = false; const bool bSuccess = GetDerivedDataCacheRef().GetSynchronous(GeometryCollectionCooker, DDCData, &bBuilt); COOK_STAT(Timer.AddHitOrMiss(!bSuccess || bBuilt ? FCookStats::CallStats::EHitOrMiss::Miss : FCookStats::CallStats::EHitOrMiss::Hit, DDCData.Num())); } if (bCopyFromDDC) { FMemoryReader Ar(DDCData, true); // Must be persistent for BulkData to serialize Chaos::FChaosArchive ChaosAr(Ar); GeometryCollection->Serialize(ChaosAr); } } } void UGeometryCollection::CreateSimulationData() { CreateSimulationDataImp(/*bCopyFromDDC=*/false); SimulationDataGuid = StateGuid; } void UGeometryCollection::CreateSimulationDataIfNeeded() { if (IsSimulationDataDirty() || bGeometryCollectionAlwaysRecreateSimulationData) { CreateSimulationData(); } } void UGeometryCollection::CreateRenderDataImp(bool bCopyFromDDC) { COOK_STAT(auto Timer = GeometryCollectionCookStats::UsageStats.TimeSyncWork()); // Skips the DDC fetch entirely for testing the builder without adding to the DDC const static bool bSkipDDC = false; //Use the DDC to build simulation data. If we are loading in the editor we then serialize this data into the geometry collection TArray DDCData; FDerivedDataGeometryCollectionRenderDataCooker* GeometryCollectionCooker = new FDerivedDataGeometryCollectionRenderDataCooker(*this); if (GeometryCollectionCooker->CanBuild()) { if (bSkipDDC) { GeometryCollectionCooker->Build(DDCData); COOK_STAT(Timer.AddMiss(DDCData.Num())); } else { bool bBuilt = false; const bool bSuccess = GetDerivedDataCacheRef().GetSynchronous(GeometryCollectionCooker, DDCData, &bBuilt); COOK_STAT(Timer.AddHitOrMiss(!bSuccess || bBuilt ? FCookStats::CallStats::EHitOrMiss::Miss : FCookStats::CallStats::EHitOrMiss::Hit, DDCData.Num())); } if (bCopyFromDDC) { FMemoryReader Ar(DDCData, true); // Must be persistent for BulkData to serialize Chaos::FChaosArchive ChaosAr(Ar); RenderData = MakeUnique(); RenderData->Serialize(ChaosAr, *this); } } } void UGeometryCollection::RebuildRenderData() { if (RenderDataGuid != StateGuid) { // Release RenderData render resources (happens on render thread) and defer deletion to the render thread for after ReleaseResources. if (FGeometryCollectionRenderData* RenderDataToDelete = RenderData.Release()) { RenderDataToDelete->ReleaseResources(); ENQUEUE_RENDER_COMMAND(SetResourceRenderThread)([RenderDataToDelete](FRHICommandListImmediate& RHICmdList) { delete RenderDataToDelete; }); } // Create new RenderData and initialize render resources (happens on render thread). RenderData = FGeometryCollectionRenderData::Create(*GetGeometryCollection(), EnableNanite, bEnableNaniteFallback, bUseFullPrecisionUVs, bConvertVertexColorsToSRGB); InitResources(); PropagateMarkDirtyToComponents(); RenderDataGuid = StateGuid; } } void UGeometryCollection::PropagateMarkDirtyToComponents() const { for (TObjectIterator It(RF_ClassDefaultObject, false, EInternalObjectFlags::Garbage); It; ++It) { if (It->RestCollection == this) { It->MarkRenderStateDirty(); It->MarkRenderDynamicDataDirty(); } } } void UGeometryCollection::PropagateTransformUpdateToComponents() const { for (TObjectIterator It(RF_ClassDefaultObject, false, EInternalObjectFlags::Garbage); It; ++It) { if (It->RestCollection == this) { // make sure to reset the rest collection to make sure the internal state of the components is up to date // but we do not apply asset default to avoid overriding the existing overrides It->SetRestCollection(this, false /* bApplyAssetDefaults */); } } } void UGeometryCollection::SetRootProxiesFromGeometrySources() { RootProxyData.ProxyMeshes.Reset(); RootProxyData.MeshTransforms.Reset(); // make sure the root index is properly cached if (RootIndex == INDEX_NONE) { UpdateRootIndex(); } FTransform3f RootTransform = FTransform3f::Identity; if (GeometryCollection && GeometryCollection->Transform.IsValidIndex(RootIndex)) { RootTransform = GeometryCollection->Transform[RootIndex]; } for (const FGeometryCollectionSource& Source : GeometrySource) { UObject* SourceMesh = Source.SourceGeometryObject.TryLoad(); if (UStaticMesh* SourceStaticMesh = Cast(SourceMesh)) { RootProxyData.ProxyMeshes.Add(SourceStaticMesh); const FTransform3f MeshTransform = FTransform3f(Source.LocalTransform).GetRelativeTransform(RootTransform); RootProxyData.MeshTransforms.Add(MeshTransform); } } } TSharedPtr UGeometryCollection::GenerateMinimalGeometryCollection() const { TMap> SkipList; static TSet GeometryGroups{ FGeometryCollection::GeometryGroup, FGeometryCollection::VerticesGroup, FGeometryCollection::FacesGroup }; if (bStripOnCook) { // Remove all geometry //static TSet GeometryGroups{ FGeometryCollection::GeometryGroup, FGeometryCollection::VerticesGroup, FGeometryCollection::FacesGroup, FGeometryCollection::MaterialGroup }; for (const FName& GeometryGroup : GeometryGroups) { TSet& SkipAttributes = SkipList.Add(GeometryGroup); SkipAttributes.Append(GeometryCollection->AttributeNames(GeometryGroup)); } } TSharedPtr DuplicateGeometryCollection(new FGeometryCollection()); DuplicateGeometryCollection->AddAttribute(FGeometryCollection::SimulatableParticlesAttribute, FTransformCollection::TransformGroup); DuplicateGeometryCollection->AddAttribute("InertiaTensor", FGeometryCollection::TransformGroup); DuplicateGeometryCollection->AddAttribute("Mass", FGeometryCollection::TransformGroup); DuplicateGeometryCollection->AddAttribute("MassToLocal", FGeometryCollection::TransformGroup); DuplicateGeometryCollection->AddAttribute( FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup); DuplicateGeometryCollection->CopyMatchingAttributesFrom(*GeometryCollection, &SkipList); // If we've removed all geometry, we need to make sure any references to that geometry are removed. // We also need to resize geometry groups to ensure that they are empty. if (bStripOnCook) { const TManagedArray& TransformToGeometryIndex = DuplicateGeometryCollection->GetAttribute("TransformToGeometryIndex", FTransformCollection::TransformGroup); // // Copy the bounds to the TransformGroup. // @todo(nanite.bounds) : Rely on Nanite bounds in the component instead and dont copy here // if (!DuplicateGeometryCollection->HasAttribute("BoundingBox", "Transform")) { DuplicateGeometryCollection->AddAttribute("BoundingBox", "Transform"); } const int32 NumTransforms = GeometryCollection->NumElements(FGeometryCollection::TransformGroup); TManagedArray& TransformBounds = DuplicateGeometryCollection->ModifyAttribute("BoundingBox", "Transform"); const TManagedArray& GeometryBounds = GeometryCollection->GetAttribute("BoundingBox", "Geometry"); for (int TransformIndex = 0; TransformIndex < NumTransforms; TransformIndex++) { const int32 GeometryIndex = TransformToGeometryIndex[TransformIndex]; if (GeometryIndex != INDEX_NONE) { TransformBounds[TransformIndex] = GeometryBounds[GeometryIndex]; } else { TransformBounds[TransformIndex].Init(); } } // // Clear the geometry and the transforms connection to it. // //TransformToGeometryIndex.Fill(INDEX_NONE); for (const FName& GeometryGroup : GeometryGroups) { DuplicateGeometryCollection->EmptyGroup(GeometryGroup); } } return DuplicateGeometryCollection; } TSharedPtr UGeometryCollection::CopyCollectionAndRemoveGeometry(const TSharedPtr& CollectionToCopy) { TSharedPtr GeometryCollectionToReturn(new FGeometryCollection()); const TArray GroupsToSkip{ FGeometryCollection::GeometryGroup, FGeometryCollection::VerticesGroup, FGeometryCollection::FacesGroup }; const TArray> AttributesToSkip{ { FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup } }; CollectionToCopy->CopyTo(GeometryCollectionToReturn.Get(), GroupsToSkip, AttributesToSkip); if (FGeometryCollection::AreCollisionParticlesEnabled()) { // recreate the simplicial attribute since we cannot copy it and we skipped it using FSimplicialUniquePtr = TUniquePtr; if (const TManagedArray* SourceSimplicials = CollectionToCopy->FindAttribute(FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup)) { TManagedArray& SimplicialsToWrite = GeometryCollectionToReturn->AddAttribute(FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup); for (int32 Index = SourceSimplicials->Num() - 1; 0 <= Index; Index--) { SimplicialsToWrite[Index].Reset((*SourceSimplicials)[Index] ? (*SourceSimplicials)[Index]->NewCopy() : nullptr); } } } // since we are removing the bounding box attribute from the geometry group we need to move it to the transform group const TManagedArray& GeometryBounds = CollectionToCopy->GetAttribute("BoundingBox", "Geometry"); const TManagedArray& TransformToGeometryIndexArray = CollectionToCopy->TransformToGeometryIndex; TManagedArray& TransformBounds = GeometryCollectionToReturn->AddAttribute("BoundingBox", "Transform"); for (int TransformIndex = 0; TransformIndex < TransformBounds.Num(); TransformIndex++) { const int32 GeometryIndex = TransformToGeometryIndexArray[TransformIndex]; if (GeometryIndex != INDEX_NONE) { TransformBounds[TransformIndex] = GeometryBounds[GeometryIndex]; } else { TransformBounds[TransformIndex].Init(); } } return GeometryCollectionToReturn; } #endif FGeometryCollectionRenderResourceSizeInfo UGeometryCollection::GetRenderResourceSizeInfo() const { FGeometryCollectionRenderResourceSizeInfo InfoOut; const FGeometryCollectionMeshResources& MeshResources = RenderData->MeshResource; InfoOut.MeshResourcesSize += MeshResources.IndexBuffer.GetIndexDataSize(); InfoOut.MeshResourcesSize += MeshResources.PositionVertexBuffer.GetAllocatedSize(); InfoOut.MeshResourcesSize += MeshResources.StaticMeshVertexBuffer.GetResourceSize(); InfoOut.MeshResourcesSize += MeshResources.ColorVertexBuffer.GetAllocatedSize(); InfoOut.MeshResourcesSize += MeshResources.BoneMapVertexBuffer.GetAllocatedSize(); InfoOut.NaniteResourcesSize += GetNaniteResourcesSize(*RenderData->NaniteResourcesPtr); return InfoOut; } void UGeometryCollection::InitResources() { if (RenderData) { RenderData->InitResources(*this); } } void UGeometryCollection::ReleaseResources() { if (RenderData) { RenderData->ReleaseResources(); } } void UGeometryCollection::InvalidateCollection() { StateGuid = FGuid::NewGuid(); UpdateRootIndex(); CacheBreadthFirstTransformIndices(); } #if WITH_EDITOR bool UGeometryCollection::IsSimulationDataDirty() const { return StateGuid != SimulationDataGuid; } #endif int32 UGeometryCollection::AttachEmbeddedGeometryExemplar(const UStaticMesh* Exemplar) { FSoftObjectPath NewExemplarPath(Exemplar); // Check first if the exemplar is already attached for (int32 ExemplarIndex = 0; ExemplarIndex < EmbeddedGeometryExemplar.Num(); ++ExemplarIndex) { if (NewExemplarPath == EmbeddedGeometryExemplar[ExemplarIndex].StaticMeshExemplar) { return ExemplarIndex; } } return EmbeddedGeometryExemplar.Emplace( NewExemplarPath ); } void UGeometryCollection::RemoveExemplars(const TArray& SortedRemovalIndices) { if (SortedRemovalIndices.Num() > 0) { for (int32 Index = SortedRemovalIndices.Num() - 1; Index >= 0; --Index) { EmbeddedGeometryExemplar.RemoveAt(Index); } } } int32 FGeometryCollectionAutoInstanceMesh::GetNumDataPerInstance() const { return NumInstances? (CustomData.Num() / NumInstances): 0; } bool FGeometryCollectionAutoInstanceMesh::operator ==(const FGeometryCollectionAutoInstanceMesh& Other) const { return (Mesh == Other.Mesh) && (Materials == Other.Materials); } /** find or add a auto instance mesh and return its index */ const FGeometryCollectionAutoInstanceMesh& UGeometryCollection::GetAutoInstanceMesh(int32 AutoInstanceMeshIndex) const { return AutoInstanceMeshes[AutoInstanceMeshIndex]; } /** find or add a auto instance mesh from another one and return its index */ int32 UGeometryCollection::FindOrAddAutoInstanceMesh(const FGeometryCollectionAutoInstanceMesh& AutoInstanceMesh) { int32 AutoInstanceMeshIndex = AutoInstanceMeshes.AddUnique(AutoInstanceMesh); FGeometryCollectionAutoInstanceMesh& Instance = AutoInstanceMeshes[AutoInstanceMeshIndex]; Instance.NumInstances++; return AutoInstanceMeshIndex; } int32 UGeometryCollection::FindOrAddAutoInstanceMesh(const UStaticMesh* StaticMesh, const TArray& MeshMaterials) { FGeometryCollectionAutoInstanceMesh NewMesh; NewMesh.Mesh = StaticMesh; NewMesh.Materials = MeshMaterials; return FindOrAddAutoInstanceMesh(NewMesh); } void UGeometryCollection::SetAutoInstanceMeshes(const TArray& InAutoInstanceMeshes) { AutoInstanceMeshes = InAutoInstanceMeshes; // dedup array and reassign indices if (AutoInstanceMeshes.Num() > 0) { TArray UniqueAutoInstanceMeshes; TArray InstanceMeshIndexRemap; UniqueAutoInstanceMeshes.Reserve(AutoInstanceMeshes.Num()); InstanceMeshIndexRemap.Reserve(AutoInstanceMeshes.Num()); // now we may have two similar entries we need to consolidate them for (int32 InstanceMeshIndex = 0; InstanceMeshIndex < AutoInstanceMeshes.Num(); InstanceMeshIndex++) { const FGeometryCollectionAutoInstanceMesh& InstanceMesh = AutoInstanceMeshes[InstanceMeshIndex]; int32 UniqueInstanceMeshIndex = UniqueAutoInstanceMeshes.Find(InstanceMesh); if (UniqueInstanceMeshIndex == INDEX_NONE) { FGeometryCollectionAutoInstanceMesh UniqueInstanceMesh = InstanceMesh; UniqueInstanceMesh.NumInstances = 0; UniqueInstanceMesh.CustomData.Reset(); UniqueInstanceMeshIndex = UniqueAutoInstanceMeshes.Add(UniqueInstanceMesh); } // make sure num instances are aggregated UniqueAutoInstanceMeshes[UniqueInstanceMeshIndex].NumInstances += InstanceMesh.NumInstances; InstanceMeshIndexRemap.Add(UniqueInstanceMeshIndex); } GeometryCollection::Facades::FCollectionInstancedMeshFacade InstancedMeshFacade(*GetGeometryCollection()); const TManagedArray>& Children = GetGeometryCollection()->Children; // relocate custom data : we cannot just aggregate them because we may have interleaved transform indices with alternating colors // also adjust the transform index to instance mesh index via the facade TArray DataReadOffsets; DataReadOffsets.SetNumZeroed(AutoInstanceMeshes.Num()); for (int32 TransformIndex = 0; TransformIndex < InstancedMeshFacade.GetNumIndices(); TransformIndex++) { // only for leaves if (Children[TransformIndex].Num() == 0) { const int32 OldIndex = InstancedMeshFacade.GetIndex(TransformIndex); if (InstanceMeshIndexRemap.IsValidIndex(OldIndex)) { const FGeometryCollectionAutoInstanceMesh& OldInstanceMesh = AutoInstanceMeshes[OldIndex]; const int32 NewIndex = InstanceMeshIndexRemap[OldIndex]; FGeometryCollectionAutoInstanceMesh& NewInstanceMesh = UniqueAutoInstanceMeshes[NewIndex]; InstancedMeshFacade.SetIndex(TransformIndex, NewIndex); const int32 NumDataPerInstance = OldInstanceMesh.GetNumDataPerInstance(); if (NumDataPerInstance > 0) { const int32 DataReadOffset = DataReadOffsets[OldIndex]; for (int32 DataIndex = 0; DataIndex < NumDataPerInstance; DataIndex++) { const float OldData = OldInstanceMesh.CustomData[DataReadOffset + DataIndex]; NewInstanceMesh.CustomData.Add(OldData); } DataReadOffsets[OldIndex] += NumDataPerInstance; } } } } AutoInstanceMeshes = MoveTemp(UniqueAutoInstanceMeshes); } } FGuid UGeometryCollection::GetIdGuid() const { return PersistentGuid; } FGuid UGeometryCollection::GetStateGuid() const { return StateGuid; } #if WITH_EDITOR void UGeometryCollection::PostEditUndo() { PropagateTransformUpdateToComponents(); Super::PostEditUndo(); } void UGeometryCollection::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { bool bDoInvalidateCollection = false; bool bValidateSizeSpecificDataDefaults = false; bool bDoUpdateConvexGeometry = false; bool bRebuildSimulationData = false; bool bRebuildRenderData = false; if (PropertyChangedEvent.Property) { FName PropertyName = PropertyChangedEvent.Property->GetFName(); if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, EnableNanite)) { bDoInvalidateCollection = true; bRebuildRenderData = true; } else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, bEnableNaniteFallback)) { bDoInvalidateCollection = true; bRebuildRenderData = true; } else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, bUseFullPrecisionUVs)) { bDoInvalidateCollection = true; bRebuildRenderData = true; } else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, bConvertVertexColorsToSRGB)) { bRebuildRenderData = true; } else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, SizeSpecificData)) { bDoInvalidateCollection = true; bDoUpdateConvexGeometry = true; bValidateSizeSpecificDataDefaults = true; bRebuildSimulationData = true; } else if (PropertyName.ToString().Contains(FString("ImplicitType"))) //SizeSpecificData.Num() && SizeSpecificData[0].CollisionShapes.Num() && // PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollection, SizeSpecificData[0].CollisionShapes[0].ImplicitType)) { bDoInvalidateCollection = true; bDoUpdateConvexGeometry = true; bRebuildSimulationData = true; } else if (PropertyChangedEvent.Property->GetFName() != GET_MEMBER_NAME_CHECKED(UGeometryCollection, Materials)) { bDoInvalidateCollection = true; bRebuildSimulationData = true; } } else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::Unspecified) { // We get here on undo/redo operations. // Make sure that render data rebuilds. bRebuildRenderData = true; } if (bDoInvalidateCollection) { InvalidateCollection(); } if (bValidateSizeSpecificDataDefaults) { ValidateSizeSpecificDataDefaults(); } if (bDoUpdateConvexGeometry) { UpdateConvexGeometry(); } if (bRebuildSimulationData) { if (!bManualDataCreate) { CreateSimulationData(); } } if (bRebuildRenderData) { RebuildRenderData(); } InvalidateDataflowContents(); Super::PostEditChangeProperty(PropertyChangedEvent); } bool UGeometryCollection::Modify(bool bAlwaysMarkDirty /*= true*/) { bool bSuperResult = Super::Modify(bAlwaysMarkDirty); UPackage* Package = GetOutermost(); if (Package->IsDirty()) { InvalidateCollection(); } return bSuperResult; } void UGeometryCollection::EnsureDataIsCooked(bool bInitResources, bool bIsTransacting, bool bIsPersistant, bool bAllowCopyFromDDC) { EnsureSimulationDataIsCooked(bIsTransacting, bAllowCopyFromDDC); EnsureRenderDataIsCooked(bInitResources); } void UGeometryCollection::EnsureSimulationDataIsCooked(bool bIsTransacting, bool bAllowCopyFromDDC = true) { if (StateGuid != LastBuiltSimulationDataGuid) { CreateSimulationDataImp(/*bCopyFromDDC=*/ bAllowCopyFromDDC && !bIsTransacting); LastBuiltSimulationDataGuid = StateGuid; } // todo(chaos) - this is temporary solution to make sure the data is computed accordingly if the attribute are missing // in the future we should probably get rid of this all cooker logic and have a proper dependent attribute system if (GeometryCollection) { if (FGeometryCollectionPhysicsProxy::NeedToInitializeSharedCollisionStructures(*GeometryCollection)) { FSharedSimulationParameters SharedParams; GetSharedSimulationParams(SharedParams); Chaos::FErrorReporter ErrorReporter(GetName()); BuildSimulationData(ErrorReporter, *GeometryCollection, SharedParams); // important : this is necessary to make sure we compute mass scale on the instances properly // sadly we cannot call this in BuildSimulationData because we have no access to the asset CacheMaterialDensity(); } } } void UGeometryCollection::EnsureRenderDataIsCooked(bool bInitResources) { // Render data only goes through DDC when loading and saving ( called from OnPostLoad / OnSave ) // Using DDC during edits isn't worth it especially as we use a continually mutating guid instead of a state hash. // That ensures that all edits are cache misses (slow) and unnecessarily fill up DDC disk space. if (StateGuid != LastBuiltRenderDataGuid) { CreateRenderDataImp(/*bCopyFromDDC=*/ bInitResources); if (FApp::CanEverRender() && bInitResources) { if (RenderData) { RenderData->InitResources(*this); } } LastBuiltRenderDataGuid = StateGuid; } } #endif void UGeometryCollection::PreSave(FObjectPreSaveContext SaveContext) { #if WITH_EDITOR constexpr bool bInitResources = false; constexpr bool bIsTransacting = false; constexpr bool bIsPersistant = false; // note that this has no effect on the call below constexpr bool bAllowCopyFromDDC = false; EnsureDataIsCooked(bInitResources, bIsTransacting, bIsPersistant, bAllowCopyFromDDC); #endif Super::PreSave(SaveContext); } void UGeometryCollection::PostLoad() { Super::PostLoad(); #if WITH_EDITOR constexpr bool bInitResources = true; EnsureRenderDataIsCooked(bInitResources); #else if (FApp::CanEverRender()) { InitResources(); } #endif // migrate deprecated data if necessary MigrateDeprecatedRootProxyData(); MigrateDeprecatedDataflowData(); } void UGeometryCollection::MigrateDeprecatedRootProxyData() { #if WITH_EDITORONLY_DATA if (!RootProxy_DEPRECATED.IsNull()) { if (UStaticMesh* ProxyMesh = Cast(RootProxy_DEPRECATED.TryLoad())) { RootProxyData.ProxyMeshes.Add(TObjectPtr(ProxyMesh)); } RootProxy_DEPRECATED = nullptr; } for (int32 MeshIndex = 0; MeshIndex < AutoInstanceMeshes.Num(); MeshIndex++) { FGeometryCollectionAutoInstanceMesh& AutoInstanceMesh = AutoInstanceMeshes[MeshIndex]; if (!AutoInstanceMesh.StaticMesh_DEPRECATED.IsNull()) { if (UStaticMesh* StaticMesh = Cast(AutoInstanceMesh.StaticMesh_DEPRECATED.TryLoad())) { AutoInstanceMesh.Mesh = TObjectPtr(StaticMesh); } AutoInstanceMesh.StaticMesh_DEPRECATED = nullptr; } } #endif } void UGeometryCollection::MigrateDeprecatedDataflowData() { #if WITH_EDITORONLY_DATA if (DataflowAsset != nullptr) { DataflowInstance.SetDataflowAsset(DataflowAsset); DataflowInstance.SetDataflowTerminal(FName(DataflowTerminal_DEPRECATED)); DataflowAsset = nullptr; DataflowTerminal_DEPRECATED.Empty(); // todo(ccaillaud) do not migrate the overrides until we have a solid plan to make sure they won't sync and risk of loosing them //FInstancedPropertyBag& Variables = DataflowInstance.GetMutableVariables(); //// convert all override to be string variables //for (const TPair& Pair : Overrides) //{ // const FName VariableName = FName(Pair.Key); // const FString& VariableValue = Pair.Value; // Variables.AddProperty(VariableName, EPropertyBagPropertyType::String); // Variables.SetValueString(VariableName, VariableValue); //} // clear all the deprecated values // Overrides.Empty(); } #endif } void UGeometryCollection::BeginDestroy() { Super::BeginDestroy(); ReleaseResources(); // Render fence to ensure that we complete resource release before destroy. RenderResourceReleaseFence.BeginFence(); } bool UGeometryCollection::IsReadyForFinishDestroy() { return Super::IsReadyForFinishDestroy() && RenderResourceReleaseFence.IsFenceComplete(); } bool UGeometryCollection::HasMeshData() const { return RenderData != nullptr && RenderData->bHasMeshData; } bool UGeometryCollection::HasNaniteData() const { return RenderData != nullptr && RenderData->bHasNaniteData; } uint32 UGeometryCollection::GetNaniteResourceID() const { return RenderData->NaniteResourcesPtr->RuntimeResourceID; } uint32 UGeometryCollection::GetNaniteHierarchyOffset() const { return RenderData->NaniteResourcesPtr->HierarchyOffset; } uint32 UGeometryCollection::GetNaniteHierarchyOffset(int32 GeometryIndex, bool bFlattened) const { const Nanite::FResources& NaniteResources = *RenderData->NaniteResourcesPtr; check(GeometryIndex >= 0 && GeometryIndex < NaniteResources.HierarchyRootOffsets.Num()); uint32 HierarchyOffset = NaniteResources.HierarchyRootOffsets[GeometryIndex]; // Translate the root offset into dwords here HierarchyOffset *= NANITE_HIERARCHY_NODE_SLICE_SIZE_DWORDS; if (bFlattened) { HierarchyOffset += NaniteResources.HierarchyOffset; } return HierarchyOffset; } void UGeometryCollection::AddAssetUserData(UAssetUserData* InUserData) { if (InUserData != nullptr) { UAssetUserData* ExistingData = GetAssetUserDataOfClass(InUserData->GetClass()); if (ExistingData != nullptr) { AssetUserData.Remove(ExistingData); } AssetUserData.Add(InUserData); } } UAssetUserData* UGeometryCollection::GetAssetUserDataOfClass(TSubclassOf InUserDataClass) { for (int32 DataIdx = 0; DataIdx < AssetUserData.Num(); DataIdx++) { UAssetUserData* Datum = AssetUserData[DataIdx]; if (Datum != nullptr && Datum->IsA(InUserDataClass)) { return Datum; } } return nullptr; } void UGeometryCollection::RemoveUserDataOfClass(TSubclassOf InUserDataClass) { for (int32 DataIdx = 0; DataIdx < AssetUserData.Num(); DataIdx++) { UAssetUserData* Datum = AssetUserData[DataIdx]; if (Datum != nullptr && Datum->IsA(InUserDataClass)) { AssetUserData.RemoveAt(DataIdx); return; } } } const TArray* UGeometryCollection::GetAssetUserDataArray() const { return &ToRawPtrTArrayUnsafe(AssetUserData); } void UGeometryCollection::SetDataflowAsset(UDataflow* InDataflowAsset) { DataflowInstance.SetDataflowAsset(InDataflowAsset); } UDataflow* UGeometryCollection::GetDataflowAsset() const { return DataflowInstance.GetDataflowAsset(); } TObjectPtr UGeometryCollection::CreateDataflowContent() { TObjectPtr BaseContent = NewObject(); BaseContent->SetDataflowOwner(this); BaseContent->SetTerminalAsset(this); WriteDataflowContent(BaseContent); return BaseContent; } void UGeometryCollection::WriteDataflowContent(const TObjectPtr& DataflowContent) const { if(const TObjectPtr BaseContent = Cast(DataflowContent)) { BaseContent->SetDataflowAsset(DataflowInstance.GetDataflowAsset()); BaseContent->SetDataflowTerminal(DataflowInstance.GetDataflowTerminal().ToString()); } } void UGeometryCollection::ReadDataflowContent(const TObjectPtr& DataflowContent) {} #if WITH_EDITOR bool UGeometryCollection::CanEditChange(const FProperty* InProperty) const { if (!Super::CanEditChange(InProperty)) { return false; } const FName& Name = InProperty->GetFName(); if (Name == GET_MEMBER_NAME_CHECKED(ThisClass, bOptimizeConvexes)) { return Chaos::CVars::bChaosConvexSimplifyUnion == true; } return true; } #endif