Files
UnrealEngineUWP/Engine/Source/Runtime/Experimental/GeometryCollectionEngine/Private/GeometryCollection/GeometryCollectionObject.cpp
graham wihlidal 1d6d62f191 Added initial functionality for building and serializing Nanite cluster pages (currently) 1:1 with Chaos geometry collection groups. This feature is still very much a work in progress.
#rb michael.lentine
#fyi michal.valient, brian.karis, benn.gallagher

[CL 13861053 by graham wihlidal in ue5-main branch]
2020-07-15 01:49:28 -04:00

667 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
GeometryCollection.cpp: UGeometryCollection methods.
=============================================================================*/
#include "GeometryCollection/GeometryCollectionObject.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionCache.h"
#include "UObject/DestructionObjectVersion.h"
#include "UObject/UE5MainStreamObjectVersion.h"
#include "Serialization/ArchiveCountMem.h"
#include "HAL/IConsoleManager.h"
#include "UObject/Package.h"
#include "Materials/MaterialInstance.h"
#include "ProfilingDebugging/CookStats.h"
#if WITH_EDITOR
#include "GeometryCollection/DerivedDataGeometryCollectionCooker.h"
#include "DerivedDataCacheInterface.h"
#include "Serialization/MemoryReader.h"
#include "NaniteBuilder.h"
#include "Rendering/NaniteResources.h"
#endif
#include "GeometryCollection/GeometryCollectionSimulationCoreTypes.h"
#include "Chaos/ChaosArchive.h"
#include "GeometryCollectionProxyData.h"
DEFINE_LOG_CATEGORY_STATIC(UGeometryCollectionLogging, NoLogging, All);
#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
UGeometryCollection::UGeometryCollection(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, EnableNanite(false)
, CollisionType(ECollisionTypeEnum::Chaos_Surface_Volumetric)
, ImplicitType(EImplicitTypeEnum::Chaos_Implicit_Box)
, MinLevelSetResolution(10)
, MaxLevelSetResolution(10)
, MinClusterLevelSetResolution(50)
, MaxClusterLevelSetResolution(50)
, CollisionObjectReductionPercentage(0.0f)
, bMassAsDensity(false)
, Mass(1.0f)
, MinimumMassClamp(0.1f)
, CollisionParticlesFraction(1.0f)
, MaximumCollisionParticles(60)
, EnableRemovePiecesOnFracture(false)
, bRenderingResourcesInitialized(false)
, GeometryCollection(new FGeometryCollection())
{
PersistentGuid = FGuid::NewGuid();
InvalidateCollection();
}
FGeometryCollectionSizeSpecificData::FGeometryCollectionSizeSpecificData()
: MaxSize(0.0f)
, CollisionType(ECollisionTypeEnum::Chaos_Surface_Volumetric)
, ImplicitType(EImplicitTypeEnum::Chaos_Implicit_Box)
, MinLevelSetResolution(5)
, MaxLevelSetResolution(10)
, MinClusterLevelSetResolution(25)
, MaxClusterLevelSetResolution(50)
, CollisionObjectReductionPercentage(0.0f)
, CollisionParticlesFraction(1.0f)
, MaximumCollisionParticles(60)
{
}
void FillSharedSimulationSizeSpecificData(FSharedSimulationSizeSpecificData& ToData, const FGeometryCollectionSizeSpecificData& FromData)
{
ToData.CollisionType = FromData.CollisionType;
ToData.ImplicitType = FromData.ImplicitType;
ToData.MaxSize = FromData.MaxSize;
ToData.MinLevelSetResolution = FromData.MinLevelSetResolution;
ToData.MaxLevelSetResolution = FromData.MaxLevelSetResolution;
ToData.MinClusterLevelSetResolution = FromData.MinClusterLevelSetResolution;
ToData.MaxClusterLevelSetResolution = FromData.MaxClusterLevelSetResolution;
ToData.CollisionObjectReductionPercentage = FromData.CollisionObjectReductionPercentage;
ToData.CollisionParticlesFraction = FromData.CollisionParticlesFraction;
ToData.MaximumCollisionParticles = FromData.MaximumCollisionParticles;
}
float KgCm3ToKgM3(float Density)
{
return Density * 1000000;
}
float KgM3ToKgCm3(float Density)
{
return Density / 1000000;
}
void UGeometryCollection::GetSharedSimulationParams(FSharedSimulationParameters& OutParams) const
{
OutParams.bMassAsDensity = bMassAsDensity;
OutParams.Mass = bMassAsDensity ? KgM3ToKgCm3(Mass) : Mass; //todo(ocohen): we still have the solver working in old units. This is mainly to fix ui issues. Long term need to normalize units for best precision
OutParams.MinimumMassClamp = MinimumMassClamp;
OutParams.MaximumCollisionParticleCount = MaximumCollisionParticles;
FGeometryCollectionSizeSpecificData InfSize;
InfSize.MaxSize = FLT_MAX;
InfSize.CollisionType = CollisionType;
InfSize.ImplicitType = ImplicitType;
InfSize.MinLevelSetResolution = MinLevelSetResolution;
InfSize.MaxLevelSetResolution = MaxLevelSetResolution;
InfSize.MinClusterLevelSetResolution = MinClusterLevelSetResolution;
InfSize.MaxClusterLevelSetResolution = MaxClusterLevelSetResolution;
InfSize.CollisionObjectReductionPercentage = CollisionObjectReductionPercentage;
InfSize.CollisionParticlesFraction = CollisionParticlesFraction;
InfSize.MaximumCollisionParticles = MaximumCollisionParticles;
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]);
}
if (EnableRemovePiecesOnFracture)
{
FixupRemoveOnFractureMaterials(OutParams);
}
OutParams.SizeSpecificData.Sort(); //can we do this at editor time on post edit change?
}
void UGeometryCollection::FixupRemoveOnFractureMaterials(FSharedSimulationParameters& SharedParms) const
{
// Match RemoveOnFracture materials with materials in model and record the material indices
int32 NumMaterials = Materials.Num();
for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; MaterialIndex++)
{
UMaterialInterface* MaterialInfo = Materials[MaterialIndex];
for (int32 ROFMaterialIndex = 0; ROFMaterialIndex < RemoveOnFractureMaterials.Num(); ROFMaterialIndex++)
{
if (MaterialInfo == RemoveOnFractureMaterials[ROFMaterialIndex])
{
SharedParms.RemoveOnFractureIndices.Add(MaterialIndex);
break;
}
}
}
}
/** 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->NumElements(Group);
}
/** RemoveElements */
void UGeometryCollection::RemoveElements(const FName & Group, const TArray<int32>& SortedDeletionList)
{
Modify();
GeometryCollection->RemoveElements(Group, SortedDeletionList);
InvalidateCollection();
}
/** ReindexMaterialSections */
void UGeometryCollection::ReindexMaterialSections()
{
Modify();
GeometryCollection->ReindexMaterials();
InvalidateCollection();
}
void UGeometryCollection::InitializeMaterials()
{
Modify();
// Consolidate materials
// add all materials to a set
TSet<UMaterialInterface*> MaterialSet;
for (UMaterialInterface* Curr : Materials)
{
MaterialSet.Add(Curr);
}
// create the final material array only containing unique materials
// and one slot for each internal material
TMap< UMaterialInterface *, int32> MaterialPtrToArrayIndex;
TArray<UMaterialInterface*> FinalMaterials;
for (UMaterialInterface *Curr : MaterialSet)
{
// Add base material
TTuple< UMaterialInterface *, int32> CurrTuple(Curr, FinalMaterials.Add(Curr));
MaterialPtrToArrayIndex.Add(CurrTuple);
// Add interior material
FinalMaterials.Add(Curr);
}
TManagedArray<int32>& MaterialID = GeometryCollection->MaterialID;
// Reassign materialid for each face given the new consolidated array of materials
for (int i = 0; i < MaterialID.Num(); ++i)
{
if (MaterialID[i] < Materials.Num())
{
UMaterialInterface* OldMaterialPtr = Materials[MaterialID[i]];
MaterialID[i] = *MaterialPtrToArrayIndex.Find(OldMaterialPtr);
}
}
// Set new material array on the collection
Materials = FinalMaterials;
// Last Material is the selection one
UMaterialInterface* BoneSelectedMaterial = LoadObject<UMaterialInterface>(NULL, TEXT("/Engine/EditorMaterials/GeometryCollection/SelectedGeometryMaterial.SelectedGeometryMaterial"), NULL, LOAD_None, NULL);
BoneSelectedMaterialIndex = Materials.Add(BoneSelectedMaterial);
GeometryCollection->ReindexMaterials();
InvalidateCollection();
}
/** Returns true if there is anything to render */
bool UGeometryCollection::HasVisibleGeometry() const
{
if(ensureMsgf(GeometryCollection.IsValid(), TEXT("Geometry Collection %s has an invalid internal collection")))
{
return GeometryCollection->HasVisibleGeometry();
}
return false;
}
/** Serialize */
void UGeometryCollection::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(FDestructionObjectVersion::GUID);
Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID);
Chaos::FChaosArchive ChaosAr(Ar);
#if WITH_EDITOR
//Early versions did not have tagged properties serialize first
if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::GeometryCollectionInDDC)
{
GeometryCollection->Serialize(ChaosAr);
}
if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::AddedTimestampedGeometryComponentCache)
{
if (Ar.IsLoading())
{
// Strip old recorded cache data
int32 DummyNumFrames;
TArray<TArray<FTransform>> 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);
}
if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::DensityUnitsChanged)
{
if (bMassAsDensity)
{
Mass = KgCm3ToKgM3(Mass);
}
}
bool bIsCookedOrCooking = Ar.IsCooking();
if (Ar.CustomVer(FDestructionObjectVersion::GUID) >= FDestructionObjectVersion::GeometryCollectionInDDC)
{
Ar << bIsCookedOrCooking;
}
#if WITH_EDITOR
if (Ar.CustomVer(FDestructionObjectVersion::GUID) == FDestructionObjectVersion::GeometryCollectionInDDC)
{
//This version only saved content into DDC so skip serializing, but copy from DDC at a specific version
bool bCopyFromDDC = Ar.IsLoading();
CreateSimulationDataImp(bCopyFromDDC, TEXT("8724C70A140146B5A2F4CF0C16083041"));
}
#endif
//new versions serialize geometry collection after tagged properties
if (Ar.CustomVer(FDestructionObjectVersion::GUID) >= FDestructionObjectVersion::GeometryCollectionInDDCAndAsset)
{
#if WITH_EDITOR
if (Ar.IsSaving() && !Ar.IsTransacting())
{
CreateSimulationDataImp(/*bCopyFromDDC=*/false); //make sure content is built before saving
}
#endif
GeometryCollection->Serialize(ChaosAr);
// Fix up the type change for implicits here, previously they were unique ptrs, now they're shared
TManagedArray<TUniquePtr<Chaos::FImplicitObject>>* OldAttr = GeometryCollection->FindAttributeTyped<TUniquePtr<Chaos::FImplicitObject>>(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup);
TManagedArray<TSharedPtr<Chaos::FImplicitObject, ESPMode::ThreadSafe>>* NewAttr = GeometryCollection->FindAttributeTyped<TSharedPtr<Chaos::FImplicitObject, ESPMode::ThreadSafe>>(FGeometryDynamicCollection::SharedImplicitsAttribute, FTransformCollection::TransformGroup);
if(OldAttr)
{
if(!NewAttr)
{
NewAttr = &GeometryCollection->AddAttribute<TSharedPtr<Chaos::FImplicitObject, ESPMode::ThreadSafe>>(FGeometryDynamicCollection::SharedImplicitsAttribute, FTransformCollection::TransformGroup);
const int32 NumElems = GeometryCollection->NumElements(FTransformCollection::TransformGroup);
for(int32 Index = 0; Index < NumElems; ++Index)
{
(*NewAttr)[Index] = TSharedPtr<Chaos::FImplicitObject, ESPMode::ThreadSafe>((*OldAttr)[Index].Release());
}
}
GeometryCollection->RemoveAttribute(FGeometryDynamicCollection::ImplicitsAttribute, FTransformCollection::TransformGroup);
}
}
if (Ar.CustomVer(FDestructionObjectVersion::GUID) < FDestructionObjectVersion::GroupAndAttributeNameRemapping)
{
GeometryCollection->UpdateOldAttributeNames();
InvalidateCollection();
#if WITH_EDITOR
CreateSimulationData();
#endif
}
if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) >= FUE5MainStreamObjectVersion::GeometryCollectionNaniteData)
{
int32 NumNaniteResources = Ar.IsLoading() ? 0 : NaniteResources.Num();
if (!EnableNanite)
{
// Avoid serializing stale Nanite data when support is disabled.
// TODO: Should instead do this on toggle of the property?
// TODO: Might never even need this, as NaniteResources may always be empty due to CreateNaniteData() running before
NumNaniteResources = 0;
}
Ar << NumNaniteResources;
if (NumNaniteResources == 0)
{
NaniteResources.Empty();
}
else
{
if (Ar.IsLoading())
{
NaniteResources.Reset(NumNaniteResources);
NaniteResources.AddDefaulted(NumNaniteResources);
}
for (int32 ResourceIndex = 0; ResourceIndex < NumNaniteResources; ++ResourceIndex)
{
NaniteResources[ResourceIndex].Serialize(ChaosAr, this);
}
// Nanite data is currently 1:1 with each geometry group in the collection.
const int32 NumGeometryGroups = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
if (NumGeometryGroups != NumNaniteResources)
{
Ar.SetError();
}
}
if (!EnableNanite)
{
// Avoid serializing stale Nanite data when support is disabled.
// TODO: Should instead do this on toggle of the property?
NaniteResources.Empty();
}
}
#if WITH_EDITOR
//for all versions loaded, make sure sim data is up to date
if (Ar.IsLoading())
{
CreateSimulationDataImp(/*bCopyFromDDC=*/ true); //make sure loaded content is built
}
#endif
}
#if WITH_EDITOR
void UGeometryCollection::CreateSimulationDataImp(bool bCopyFromDDC, const TCHAR* OverrideVersion)
{
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<uint8> DDCData;
FDerivedDataGeometryCollectionCooker* GeometryCollectionCooker = new FDerivedDataGeometryCollectionCooker(*this);
GeometryCollectionCooker->SetOverrideVersion(OverrideVersion);
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);
Chaos::FChaosArchive ChaosAr(Ar);
GeometryCollection->Serialize(ChaosAr);
}
}
}
void UGeometryCollection::CreateSimulationData()
{
CreateSimulationDataImp(/*bCopyFromDDC=*/false);
}
TArray<Nanite::FResources>& UGeometryCollection::CreateNaniteData(FGeometryCollection* Collection)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("FDerivedDataGeometryCollectionCooker::Build::Nanite"));
NaniteResources.Empty();
if (!EnableNanite)
{
return NaniteResources;
}
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
// Transform Group
const TManagedArray<int32>& TransformToGeometryIndexArray = Collection->TransformToGeometryIndex;
const TManagedArray<int32>& SimulationTypeArray = Collection->SimulationType;
const TManagedArray<int32>& StatusFlagsArray = Collection->StatusFlags;
// Vertices Group
const TManagedArray<FVector>& VertexArray = Collection->Vertex;
const TManagedArray<FVector2D>& UVArray = Collection->UV;
const TManagedArray<FLinearColor>& ColorArray = Collection->Color;
const TManagedArray<FVector>& TangentUArray = Collection->TangentU;
const TManagedArray<FVector>& TangentVArray = Collection->TangentV;
const TManagedArray<FVector>& NormalArray = Collection->Normal;
const TManagedArray<int32>& BoneMapArray = Collection->BoneMap;
// Faces Group
const TManagedArray<FIntVector>& IndicesArray = Collection->Indices;
const TManagedArray<bool>& VisibleArray = Collection->Visible;
const TManagedArray<int32>& MaterialIndexArray = Collection->MaterialIndex;
const TManagedArray<int32>& MaterialIDArray = Collection->MaterialID;
// Geometry Group
const TManagedArray<int32>& TransformIndexArray = Collection->TransformIndex;
const TManagedArray<FBox>& BoundingBoxArray = Collection->BoundingBox;
const TManagedArray<float>& InnerRadiusArray = Collection->InnerRadius;
const TManagedArray<float>& OuterRadiusArray = Collection->OuterRadius;
const TManagedArray<int32>& VertexStartArray = Collection->VertexStart;
const TManagedArray<int32>& VertexCountArray = Collection->VertexCount;
const TManagedArray<int32>& FaceStartArray = Collection->FaceStart;
const TManagedArray<int32>& FaceCountArray = Collection->FaceCount;
// Material Group
const TManagedArray<FGeometryCollectionSection>& Sections = Collection->Sections;
int32 NumGeometry = Collection->NumElements(FGeometryCollection::GeometryGroup);
NaniteResources.AddDefaulted(NumGeometry);
for (int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; GeometryGroupIndex++)
{
Nanite::FResources& NaniteResource = NaniteResources[GeometryGroupIndex];
NaniteResource = {};
uint32 NumTexCoords = 1;// NumTextureCoord;
bool bHasColors = false;// true;
const int32 VertexStart = VertexStartArray[GeometryGroupIndex];
const int32 VertexCount = VertexCountArray[GeometryGroupIndex];
TArray<FStaticMeshBuildVertex> BuildVertices;
BuildVertices.Reserve(VertexCount);
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
{
FStaticMeshBuildVertex& Vertex = BuildVertices.Emplace_GetRef();
Vertex.Position = VertexArray[VertexStart + VertexIndex];
Vertex.Color = FColor::White;
Vertex.UVs[0] = UVArray[VertexStart + VertexIndex];
}
const int32 FaceStart = FaceStartArray[GeometryGroupIndex];
const int32 FaceCount = FaceCountArray[GeometryGroupIndex];
TArray<FStaticMeshSection, TInlineAllocator<1>> BuildSections;
// TODO: Respect multiple materials like in FGeometryCollectionConversion::AppendStaticMesh
// TODO: Cleanup super section hack
BuildSections.Reserve(FaceCount);
TArray<uint32> BuildIndices;
BuildIndices.Reserve(FaceCount * 3);
for (int32 FaceIndex = 0; FaceIndex < FaceCount; ++FaceIndex)
{
const FIntVector FaceIndices = IndicesArray[FaceStart + FaceIndex];
BuildIndices.Add(FaceIndices.X - VertexStart);// -(FaceStart * 3));
BuildIndices.Add(FaceIndices.Y - VertexStart);// -(FaceStart * 3));
BuildIndices.Add(FaceIndices.Z - VertexStart);// -(FaceStart * 3));
// TODO: Cleanup super hack
FStaticMeshSection& BuildSection = BuildSections.Emplace_GetRef();
BuildSection.MaterialIndex = MaterialIDArray[FaceStart + FaceIndex];
BuildSection.FirstIndex = FaceIndex * 3;
BuildSection.MinVertexIndex = BuildSection.FirstIndex;
BuildSection.MaxVertexIndex = BuildSection.FirstIndex;
BuildSection.NumTriangles = 1;
}
FMeshNaniteSettings NaniteSettings = {};
NaniteSettings.bEnabled = true;
NaniteSettings.PercentTriangles = 1.0f; // 100% - no reduction
if (!NaniteBuilderModule.Build(NaniteResource, BuildVertices, BuildIndices, BuildSections, NumTexCoords, bHasColors, NaniteSettings))
{
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for geometry collection. See previous line(s) for details."));
}
}
return NaniteResources;
}
#endif
void UGeometryCollection::InitResources()
{
//LLM_SCOPE(ELLMTag::GeometryCollection);
ReleaseResources();
for (Nanite::FResources& Resource : NaniteResources)
{
// Skip resources that have their render data stripped
if (Resource.PageStreamingStates.Num() > 0)
{
Resource.InitResources();
}
}
bRenderingResourcesInitialized = true;
}
void UGeometryCollection::ReleaseResources()
{
if (bRenderingResourcesInitialized)
{
for (Nanite::FResources& Resource : NaniteResources)
{
if (Resource.PageStreamingStates.Num() > 0)
{
Resource.ReleaseResources();
}
}
bRenderingResourcesInitialized = false;
}
}
void UGeometryCollection::InvalidateCollection()
{
StateGuid = FGuid::NewGuid();
}
FGuid UGeometryCollection::GetIdGuid() const
{
return PersistentGuid;
}
FGuid UGeometryCollection::GetStateGuid() const
{
return StateGuid;
}
#if WITH_EDITOR
void UGeometryCollection::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() != GET_MEMBER_NAME_CHECKED(UGeometryCollection, Materials))
{
InvalidateCollection();
CreateSimulationData();
}
}
bool UGeometryCollection::Modify(bool bAlwaysMarkDirty /*= true*/)
{
bool bSuperResult = Super::Modify(bAlwaysMarkDirty);
UPackage* Package = GetOutermost();
if(Package->IsDirty())
{
InvalidateCollection();
}
return bSuperResult;
}
void UGeometryCollection::EnsureDataIsCooked()
{
if (StateGuid != LastBuiltGuid)
{
CreateSimulationDataImp(/*bCopyFromDDC=*/ true);
LastBuiltGuid = StateGuid;
}
}
#endif
void UGeometryCollection::PostLoad()
{
Super::PostLoad();
// initialize rendering resources
if (FApp::CanEverRender())
{
InitResources();
}
}
void UGeometryCollection::BeginDestroy()
{
Super::BeginDestroy();
ReleaseResources();
}