// Copyright Epic Games, Inc. All Rights Reserved. #include "FoliageInstanceBase.h" #include "GameFramework/Actor.h" #include "Misc/PackageName.h" #include "InstancedFoliageActor.h" #include "InstancedFoliage.h" #include "Engine/Level.h" #include "Engine/WorldComposition.h" #include "UObject/FortniteMainBranchObjectVersion.h" #if WITH_EDITORONLY_DATA FFoliageInstanceBaseInfo::FFoliageInstanceBaseInfo() : CachedLocation(FVector::ZeroVector) , CachedRotation(FRotator::ZeroRotator) , CachedDrawScale(1.0f, 1.0f, 1.0f) {} FFoliageInstanceBaseInfo::FFoliageInstanceBaseInfo(UActorComponent* InComponent) : BasePtr(InComponent) , CachedLocation(FVector::ZeroVector) , CachedRotation(FRotator::ZeroRotator) , CachedDrawScale(1.0f, 1.0f, 1.0f) { UpdateLocationFromComponent(InComponent); } void FFoliageInstanceBaseInfo::UpdateLocationFromComponent(UActorComponent* InComponent) { if (InComponent) { AActor* Owner = Cast(InComponent->GetOuter()); if (Owner) { const USceneComponent* RootComponent = Owner->GetRootComponent(); if (RootComponent) { CachedLocation = RootComponent->GetRelativeLocation(); CachedRotation = RootComponent->GetRelativeRotation(); CachedDrawScale = RootComponent->GetRelativeScale3D(); } } } } FArchive& operator << (FArchive& Ar, FFoliageInstanceBaseInfo& BaseInfo) { Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::FoliageLazyObjPtrToSoftObjPtr) { Ar << BaseInfo.BasePtr_DEPRECATED; } else { // This is a cached reference, we don't want to warn about the target actor being deleted FSoftObjectPathSerializationScope SerializationScope(NAME_None, NAME_None, ESoftObjectPathCollectType::NeverCollect, ESoftObjectPathSerializeType::AlwaysSerialize); Ar << BaseInfo.BasePtr; } Ar << BaseInfo.CachedLocation; Ar << BaseInfo.CachedRotation; Ar << BaseInfo.CachedDrawScale; return Ar; } ////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////// FFoliageInstanceBaseId FFoliageInstanceBaseCache::InvalidBaseId = INDEX_NONE; FFoliageInstanceBaseCache::FFoliageInstanceBaseCache() : NextBaseId(1) { } FArchive& operator << (FArchive& Ar, FFoliageInstanceBaseCache& InstanceBaseCache) { Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); Ar << InstanceBaseCache.NextBaseId; Ar << InstanceBaseCache.InstanceBaseMap; if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::FoliageLazyObjPtrToSoftObjPtr) { Ar << InstanceBaseCache.InstanceBaseLevelMap_DEPRECATED; } else { // This is a cached reference, we don't want to warn about the target actor being deleted FSoftObjectPathSerializationScope SerializationScope(NAME_None, NAME_None, ESoftObjectPathCollectType::NeverCollect, ESoftObjectPathSerializeType::AlwaysSerialize); Ar << InstanceBaseCache.InstanceBaseLevelMap; if (Ar.IsTransacting()) { Ar << InstanceBaseCache.InstanceBaseInvMap; } else if (Ar.IsLoading()) // Regenerate inverse map whenever we load cache { InstanceBaseCache.InstanceBaseInvMap.Empty(); for (const auto& Pair : InstanceBaseCache.InstanceBaseMap) { const FFoliageInstanceBaseInfo& BaseInfo = Pair.Value; if (InstanceBaseCache.InstanceBaseInvMap.Contains(BaseInfo.BasePtr)) { // more info for UE-30878 UE_LOG(LogInstancedFoliage, Warning, TEXT("Instance base cache - integrity verification(3): Counter: %d Size: %d, InvSize: %d (Key: %s)"), (int32)InstanceBaseCache.NextBaseId, InstanceBaseCache.InstanceBaseMap.Num(), InstanceBaseCache.InstanceBaseInvMap.Num(), *BaseInfo.BasePtr.GetUniqueID().ToString()); } else { InstanceBaseCache.InstanceBaseInvMap.Add(BaseInfo.BasePtr, Pair.Key); } } } } return Ar; } FFoliageInstanceBaseId FFoliageInstanceBaseCache::AddInstanceBaseId(UActorComponent* InComponent) { FFoliageInstanceBaseId BaseId = FFoliageInstanceBaseCache::InvalidBaseId; if (InComponent && !InComponent->IsCreatedByConstructionScript()) { BaseId = GetInstanceBaseId(InComponent); FFoliageInstanceBaseInfo BaseInfo(InComponent); if (BaseId == FFoliageInstanceBaseCache::InvalidBaseId) { // generate next unique ID for base component do { BaseId = NextBaseId++; } while (InstanceBaseMap.Contains(BaseId)); // more info for UE-30878 if (InstanceBaseInvMap.Contains(BaseInfo.BasePtr)) { FSoftObjectPath BaseUID = BaseInfo.BasePtr.GetUniqueID(); UE_LOG(LogInstancedFoliage, Error, TEXT("Instance base cache - integrity verification(2): Counter: %d Size: %d, InvSize: %d, BaseUID: %s, BaseName: %s"), (int32)BaseId, InstanceBaseMap.Num(), InstanceBaseInvMap.Num(), *BaseUID.ToString(), *InComponent->GetFullName()); } InstanceBaseMap.Add(BaseId, BaseInfo); InstanceBaseInvMap.Add(BaseInfo.BasePtr, BaseId); ULevel* ComponentLevel = InComponent->GetComponentLevel(); if (ComponentLevel) { UWorld* ComponentWorld = Cast(ComponentLevel->GetOuter()); if (ComponentWorld) { auto WorldKey = TSoftObjectPtr(ComponentWorld); InstanceBaseLevelMap.FindOrAdd(WorldKey).Add(BaseInfo.BasePtr); } } } else { // Update BaseInfo InstanceBaseMap[BaseId] = MoveTemp(BaseInfo); } } return BaseId; } FFoliageInstanceBaseId FFoliageInstanceBaseCache::GetInstanceBaseId(UActorComponent* InComponent) const { if(InComponent && (InstanceBaseInvMap.Num() > 0)) { FFoliageInstanceBasePtr BasePtr(InComponent); if (BasePtr.IsValid()) { return GetInstanceBaseId(BasePtr); } } return InvalidBaseId; } FFoliageInstanceBaseId FFoliageInstanceBaseCache::GetInstanceBaseId(const FFoliageInstanceBasePtr& BasePtr) const { const FFoliageInstanceBaseId* BaseId = InstanceBaseInvMap.Find(BasePtr); if (!BaseId) { return FFoliageInstanceBaseCache::InvalidBaseId; } return *BaseId; } FFoliageInstanceBasePtr FFoliageInstanceBaseCache::GetInstanceBasePtr(FFoliageInstanceBaseId BaseId) const { return GetInstanceBaseInfo(BaseId).BasePtr; } FFoliageInstanceBaseInfo FFoliageInstanceBaseCache::GetInstanceBaseInfo(FFoliageInstanceBaseId BaseId) const { return InstanceBaseMap.FindRef(BaseId); } FFoliageInstanceBaseInfo FFoliageInstanceBaseCache::UpdateInstanceBaseInfoTransform(UActorComponent* InComponent) { auto BaseId = GetInstanceBaseId(InComponent); if (BaseId != FFoliageInstanceBaseCache::InvalidBaseId) { auto* BaseInfo = InstanceBaseMap.Find(BaseId); check(BaseInfo); BaseInfo->UpdateLocationFromComponent(InComponent); return *BaseInfo; } return FFoliageInstanceBaseInfo(); } void FFoliageInstanceBaseCache::CompactInstanceBaseCache(AInstancedFoliageActor* IFA) { FFoliageInstanceBaseCache& Cache = IFA->InstanceBaseCache; UWorld* World = IFA->GetWorld(); if (World != nullptr) { TSet BasesInUse; for (auto& FoliagePair : IFA->FoliageInfos) { for (const auto& Pair : FoliagePair.Value->ComponentHash) { if (Pair.Key != FFoliageInstanceBaseCache::InvalidBaseId) { BasesInUse.Add(Pair.Key); } } } // Look for any removed maps TSet InvalidBasePtrs; for (auto Iter = Cache.InstanceBaseLevelMap.CreateIterator(); Iter; ++Iter) { TPair, TArray>& Pair = *Iter; const auto& WorldAsset = Pair.Key; bool bExists = (WorldAsset == World); // Check sub-levels if (!bExists) { const FName PackageName = FName(*FPackageName::ObjectPathToPackageName(WorldAsset.ToString())); if (World->WorldComposition) { bExists = World->WorldComposition->DoesTileExists(PackageName); } else { bExists = (World->GetLevelStreamingForPackageName(PackageName) != nullptr); } } if (!bExists) { InvalidBasePtrs.Append(Pair.Value); Iter.RemoveCurrent(); } else { // Remove dead links for (int32 i = Pair.Value.Num() - 1; i >= 0; --i) { // Base needs to be removed if it's not in use by existing instances or component was removed if (Pair.Value[i].IsNull() || !BasesInUse.Contains(Cache.GetInstanceBaseId(Pair.Value[i]))) { InvalidBasePtrs.Add(Pair.Value[i]); Pair.Value.RemoveAt(i); } } if (Pair.Value.Num() == 0) { Iter.RemoveCurrent(); } } } TSet InvalidBaseIds; Cache.InstanceBaseInvMap.Empty(); // Look for any removed base components for (auto Iter = Cache.InstanceBaseMap.CreateIterator(); Iter; ++Iter) { const TPair& Pair = *Iter; const FFoliageInstanceBaseInfo& BaseInfo = Pair.Value; if (InvalidBasePtrs.Contains(BaseInfo.BasePtr)) { InvalidBaseIds.Add(Pair.Key); Iter.RemoveCurrent(); } else { // Regenerate inverse map Cache.InstanceBaseInvMap.Add(BaseInfo.BasePtr, Pair.Key); } } if (InvalidBaseIds.Num()) { for (auto& Pair : IFA->FoliageInfos) { auto& MeshInfo = Pair.Value; MeshInfo->ComponentHash.Empty(); int32 InstanceIdx = 0; for (FFoliageInstance& Instance : MeshInfo->Instances) { if (InvalidBaseIds.Contains(Instance.BaseId)) { Instance.BaseId = FFoliageInstanceBaseCache::InvalidBaseId; } MeshInfo->ComponentHash.FindOrAdd(Instance.BaseId).Add(InstanceIdx); InstanceIdx++; } } Cache.InstanceBaseMap.Compact(); Cache.InstanceBaseLevelMap.Compact(); } } } void FFoliageInstanceBaseCache::UpdateInstanceBaseCachedTransforms() { for (auto& Pair : InstanceBaseMap) { FFoliageInstanceBaseInfo& BaseInfo = Pair.Value; BaseInfo.UpdateLocationFromComponent(BaseInfo.BasePtr.Get()); } } #endif//WITH_EDITORONLY_DATA