Files
UnrealEngine/Engine/Plugins/Runtime/MassGameplay/Source/MassRepresentation/Private/MassVisualizationComponent.cpp
2025-05-18 13:04:45 +08:00

757 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MassVisualizationComponent.h"
#include "CoreGlobals.h"
#include "Logging/LogMacros.h"
#include "MassEntityTypes.h"
#include "MassVisualizer.h"
#include "MassRepresentationTypes.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "RenderUtils.h"
#include "SceneInterface.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "VisualLogger/VisualLogger.h"
#include "AI/NavigationSystemBase.h"
DECLARE_CYCLE_STAT(TEXT("Mass Visualization EndVisualChanges"), STAT_Mass_VisualizationComponent_EndVisualChanges, STATGROUP_Mass);
DECLARE_CYCLE_STAT(TEXT("Mass Visualization HandleIDs"), STAT_Mass_VisualizationComponent_HandleChangesWithExternalIDTracking, STATGROUP_Mass);
DECLARE_DWORD_COUNTER_STAT(TEXT("VisualizationComp Instances Removed"), STAT_Mass_VisualizationComponent_InstancesRemovedNum, STATGROUP_Mass);
DECLARE_DWORD_COUNTER_STAT(TEXT("VisualizationComp Instances Added"), STAT_Mass_VisualizationComponent_InstancesAddedNum, STATGROUP_Mass);
//---------------------------------------------------------------
// UMassVisualizationComponent
//---------------------------------------------------------------
namespace UE::Mass::Representation
{
int32 GCallUpdateInstances = 1;
FAutoConsoleVariableRef CVarCallUpdateInstances(TEXT("Mass.CallUpdateInstances"), GCallUpdateInstances, TEXT("Toggle between UpdateInstances and BatchUpdateTransform."));
#if STATS
uint32 LastStatsResetFrame = 0;
#endif // STATS
} // UE::Mass::Representation
void UMassVisualizationComponent::PostInitProperties()
{
Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject) == false && GetOuter())
{
ensureMsgf(GetOuter()->GetClass()->IsChildOf(AMassVisualizer::StaticClass()), TEXT("UMassVisualizationComponent should only be added to AMassVisualizer-like instances"));
}
}
FStaticMeshInstanceVisualizationDescHandle UMassVisualizationComponent::AddInstancedStaticMeshInfo(const FStaticMeshInstanceVisualizationDesc& Desc)
{
FStaticMeshInstanceVisualizationDescHandle Handle;
if (InstancedStaticMeshInfosFreeIndices.Num() > 0)
{
Handle = InstancedStaticMeshInfosFreeIndices.Pop(EAllowShrinking::No);
new(&InstancedStaticMeshInfos[Handle.ToIndex()]) FMassInstancedStaticMeshInfo(Desc);
}
else
{
int32 AddedInfoIndex = InstancedStaticMeshInfos.Emplace(Desc);
Handle = FStaticMeshInstanceVisualizationDescHandle(AddedInfoIndex);
}
return Handle;
}
FStaticMeshInstanceVisualizationDescHandle UMassVisualizationComponent::FindOrAddVisualDesc(const FStaticMeshInstanceVisualizationDesc& Desc)
{
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
// First check to see if we already have a matching Desc already and reuse / return that
// Note: FStaticMeshInstanceVisualizationDescHandle(int32) handles the INDEX_NONE case here, generating an invalid handle in this case
FStaticMeshInstanceVisualizationDescHandle VisualDescHandle(InstancedStaticMeshInfos.IndexOfByPredicate([&Desc](const FMassInstancedStaticMeshInfo& Info) { return Info.GetDesc() == Desc; }));
if (!VisualDescHandle.IsValid())
{
if (Desc.IsValid())
{
for (const FMassStaticMeshInstanceVisualizationMeshDesc& MeshDesc : Desc.Meshes)
{
if (MeshDesc.Mesh && MeshDesc.ISMComponentClass)
{
// if we've already encountered MeshDesc in the past MeshDescToISMCMap already contains information
// about actual ISMC used to represent it, and at the same time indicates the ISMCSharedData data
// tied to it. Regardless we need to process all MeshDesc instances here so that we have all the
// data ready when InstancedSMComponentsRequiringConstructing gets processed next time
// UMassVisualizationComponent::ConstructStaticMeshComponents gets called.
MeshDescToISMCMap.FindOrAdd(GetTypeHash(MeshDesc), FISMCSharedDataKey());
}
}
VisualDescHandle = AddInstancedStaticMeshInfo(Desc);
check(VisualDescHandle.IsValid());
// VisualDescHandle is a valid handle now, but there's initialization pending, performed in ConstructStaticMeshComponents
InstancedSMComponentsRequiringConstructing.Add(VisualDescHandle);
}
else
{
UE_LOG(LogMassRepresentation, Warning, TEXT("%hs: invalid FStaticMeshInstanceVisualizationDesc passed in. Check the contained meshes."), __FUNCTION__);
}
}
return VisualDescHandle;
}
FStaticMeshInstanceVisualizationDescHandle UMassVisualizationComponent::AddVisualDescWithISMComponent(const FStaticMeshInstanceVisualizationDesc& Desc, UInstancedStaticMeshComponent& ISMComponent)
{
TObjectPtr<UInstancedStaticMeshComponent> AsObjectPtr = &ISMComponent;
return AddVisualDescWithISMComponents(Desc, MakeArrayView(&AsObjectPtr, 1));
}
FStaticMeshInstanceVisualizationDescHandle UMassVisualizationComponent::AddVisualDescWithISMComponents(const FStaticMeshInstanceVisualizationDesc& Desc, TArrayView<TObjectPtr<UInstancedStaticMeshComponent>> ISMComponents)
{
check(Desc.Meshes.Num() == ISMComponents.Num());
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
// 0. Iterate over all meshes in the visualization desc. Each mesh is a descriptor.
FStaticMeshInstanceVisualizationDescHandle VisualHandle;
TArray<UInstancedStaticMeshComponent*> ISMComponentsUsed;
for (int32 EntryIndex = 0; EntryIndex < Desc.Meshes.Num(); ++EntryIndex)
{
const FMassStaticMeshInstanceVisualizationMeshDesc& MeshDesc = Desc.Meshes[EntryIndex];
if (MeshDesc.Mesh == nullptr || ISMComponents[EntryIndex] == nullptr)
{
// invalid description, log an continue.
UE_VLOG_UELOG(this, LogMassRepresentation, Error, TEXT("Empty mesh at index %d while registering FStaticMeshInstanceVisualizationDesc instance"), EntryIndex);
continue;
}
// 1. Creates a VisualHandle that will be used for all ISMCs
if (!VisualHandle.IsValid())
{
VisualHandle = AddInstancedStaticMeshInfo(Desc);
check(VisualHandle.IsValid());
}
// 2. Stores one FMassISMCSharedData for each ISMC in ISMCSharedData
// NOTE: FMassISMCSharedData stores a reference to an ISMC and instance transform updates performed in the current frame.
FMassISMCSharedData& NewData = ISMCSharedData.FindOrAdd(ISMComponents[EntryIndex], FMassISMCSharedData(ISMComponents[EntryIndex], /*bInRequiresExternalInstanceIDTracking=*/true, /*InTransformOffset=*/MeshDesc.LocalTransform));
// 3. Stores the newly created FMassISMCSharedData in a VisualHandle -> FMassISMCSharedData array
// NOTE: This means that one VisualHandle may reference multiple FMassISMCSharedData
InstancedStaticMeshInfos[VisualHandle.ToIndex()].AddISMComponent(NewData);
// 4. Stores an ISMC -> VisualHandle map, which allows us to later on use an ISMC to query for its FMassISMCSharedData
ISMComponentMap.Add(ISMComponents[EntryIndex], VisualHandle);
ISMComponentsUsed.Add(ISMComponents[EntryIndex]);
}
if (VisualHandle.IsValid())
{
BuildLODSignificanceForInfo(InstancedStaticMeshInfos[VisualHandle.ToIndex()], ISMComponentsUsed);
}
return VisualHandle;
}
const FMassISMCSharedData* UMassVisualizationComponent::GetISMCSharedDataForDescriptionIndex(const int32 DescriptionIndex) const
{
return ISMCSharedData.GetDataForIndex(DescriptionIndex);
}
const FMassISMCSharedData* UMassVisualizationComponent::GetISMCSharedDataForInstancedStaticMesh(const UInstancedStaticMeshComponent* ISMC) const
{
return ISMCSharedData.GetDataForKey(ISMC);
}
void UMassVisualizationComponent::RemoveVisualDesc(const FStaticMeshInstanceVisualizationDescHandle VisualizationHandle)
{
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
if (ensure(InstancedStaticMeshInfos.IsValidIndex(VisualizationHandle.ToIndex()))
&& ensureMsgf(InstancedStaticMeshInfos[VisualizationHandle.ToIndex()].IsValid(), TEXT("Trying to remove visualization data that has already been cleaned")))
{
for (TObjectPtr<UInstancedStaticMeshComponent>& ISMComponent : InstancedStaticMeshInfos[VisualizationHandle.ToIndex()].InstancedStaticMeshComponents)
{
const bool bValidKey = ISMComponentMap.Contains(ISMComponent);
checkf(bValidKey, TEXT("Failed to find ISMC in ISMComponentMap, path: %s"), *ISMComponent.GetPathName());
if (bValidKey)
{
const FStaticMeshInstanceVisualizationDescHandle StoredVisualizationDescHandle = ISMComponentMap.FindAndRemoveChecked(ISMComponent);
ensure(StoredVisualizationDescHandle == VisualizationHandle);
}
ISMCSharedData.Remove(ISMComponent);
}
InstancedStaticMeshInfos[VisualizationHandle.ToIndex()].Reset();
InstancedStaticMeshInfosFreeIndices.Add(VisualizationHandle);
}
}
void UMassVisualizationComponent::ConstructStaticMeshComponents()
{
AActor* ActorOwner = GetOwner();
check(ActorOwner);
TArray<UInstancedStaticMeshComponent*> TransientISMCs;
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
for (const FStaticMeshInstanceVisualizationDescHandle VisualDescHandle : InstancedSMComponentsRequiringConstructing)
{
if (!ensureMsgf(InstancedStaticMeshInfos.IsValidIndex(VisualDescHandle.ToIndex())
, TEXT("InstancedStaticMeshInfos (size: %d) is never expected to shrink, so VisualDescHandle (value: %u) being invalid indicates it was wrong from the start.")
, InstancedStaticMeshInfos.Num(), VisualDescHandle.ToIndex()))
{
continue;
}
FMassInstancedStaticMeshInfo& Info = InstancedStaticMeshInfos[VisualDescHandle.ToIndex()];
// Check if it is already created
if (!Info.InstancedStaticMeshComponents.IsEmpty())
{
continue;
}
// Check if there are any specified meshes for this visual type
if(Info.Desc.Meshes.Num() == 0)
{
UE_LOG(LogMassRepresentation, Error, TEXT("No associated meshes for this instanced static mesh type"));
continue;
}
TransientISMCs.Reset();
for (const FMassStaticMeshInstanceVisualizationMeshDesc& MeshDesc : Info.Desc.Meshes)
{
// MeshDescToISMCMap here lets us figure out whether for the given MeshDesc we need to create a new ISM component
// or a one has already been created in the past. Note that we only need this intermediate map for
// FMassStaticMeshInstanceVisualizationMeshDesc that has been added to the system without specifying an
// ISM component to handle the instances (i.e. added via FindOrAddVisualDesc rather than AddVisualDescWithISMComponents).
// This is the only kind of FMassStaticMeshInstanceVisualizationMeshDesc were processing here.
FISMCSharedDataKey& ISMCKey = MeshDescToISMCMap.FindChecked(GetTypeHash(MeshDesc));
FMassISMCSharedData* SharedData = ISMCSharedData.Find(ISMCKey);
UInstancedStaticMeshComponent* ISMC = SharedData ? SharedData->GetMutableISMComponent() : nullptr;
if (ISMC == nullptr)
{
ISMC = NewObject<UInstancedStaticMeshComponent>(ActorOwner, MeshDesc.ISMComponentClass);
CA_ASSUME(ISMC);
REDIRECT_OBJECT_TO_VLOG(ISMC, this);
// note that ISMCKey is a reference, so the assignment below actually sets a value in MeshDescToISMCMap
// and all subsequent handling of a given MeshDesc configuration (i.e. containing same values) will
// result in referring to the ISMC we just created.
ISMCKey = ISMC;
ISMC->SetStaticMesh(MeshDesc.Mesh);
for (int32 ElementIndex = 0; ElementIndex < MeshDesc.MaterialOverrides.Num(); ++ElementIndex)
{
if (UMaterialInterface* MaterialOverride = MeshDesc.MaterialOverrides[ElementIndex])
{
ISMC->SetMaterial(ElementIndex, MaterialOverride);
}
}
ISMC->SetCullDistances(0, 1000000); // @todo: Need to figure out what to do here, either LOD or cull distances.
ISMC->SetupAttachment(ActorOwner->GetRootComponent());
ISMC->SetCanEverAffectNavigation(false);
ISMC->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
ISMC->SetCastShadow(MeshDesc.bCastShadows);
ISMC->Mobility = MeshDesc.Mobility;
ISMC->SetReceivesDecals(false);
ISMC->RegisterComponent();
if (SharedData == nullptr)
{
SharedData = &ISMCSharedData.Add(ISMC, FMassISMCSharedData(ISMC, MeshDesc.bRequiresExternalInstanceIDTracking, MeshDesc.LocalTransform));
}
else
{
SharedData->SetISMComponent(*ISMC);
}
ensureMsgf(ISMComponentMap.Find(ISMC) == nullptr, TEXT("We've just created the ISMC that's being used here, so this check failing indicates hash-clash."));
ISMComponentMap.Add(ISMC, VisualDescHandle);
}
TransientISMCs.Add(ISMC);
check(SharedData);
Info.AddISMComponent(*SharedData);
}
// Build the LOD significance ranges
if (TransientISMCs.Num())
{
check(Info.LODSignificanceRanges.Num() == 0);
BuildLODSignificanceForInfo(Info, TransientISMCs);
}
}
}
void UMassVisualizationComponent::BuildLODSignificanceForInfo(FMassInstancedStaticMeshInfo& Info, TConstArrayView<UInstancedStaticMeshComponent*> StaticMeshRefKeys)
{
TArray<float> AllLODSignificances;
auto UniqueInsertOrdered = [&AllLODSignificances](const float Significance)
{
int i = 0;
for (; i < AllLODSignificances.Num(); ++i)
{
// I did not use epsilon check here on purpose, because it will make it hard later meshes inside.
if (Significance == AllLODSignificances[i])
{
return;
}
if (AllLODSignificances[i] > Significance)
{
break;
}
}
AllLODSignificances.Insert(Significance, i);
};
for (const FMassStaticMeshInstanceVisualizationMeshDesc& MeshDesc : Info.Desc.Meshes)
{
UniqueInsertOrdered(MeshDesc.MinLODSignificance);
UniqueInsertOrdered(MeshDesc.MaxLODSignificance);
}
if (AllLODSignificances.Num() > 1)
{
Info.LODSignificanceRanges.SetNum(AllLODSignificances.Num() - 1);
for (int RangeIndex = 0; RangeIndex < Info.LODSignificanceRanges.Num(); ++RangeIndex)
{
FMassLODSignificanceRange& Range = Info.LODSignificanceRanges[RangeIndex];
Range.MinSignificance = AllLODSignificances[RangeIndex];
Range.MaxSignificance = AllLODSignificances[RangeIndex + 1];
Range.ISMCSharedDataPtr = &ISMCSharedData;
for (int MeshIndex = 0; MeshIndex < Info.Desc.Meshes.Num(); ++MeshIndex)
{
const FMassStaticMeshInstanceVisualizationMeshDesc& MeshDesc = Info.Desc.Meshes[MeshIndex];
const bool bAddMeshInRange = (Range.MinSignificance >= MeshDesc.MinLODSignificance && Range.MinSignificance < MeshDesc.MaxLODSignificance);
if (bAddMeshInRange)
{
checkf(StaticMeshRefKeys.IsValidIndex(MeshIndex) && StaticMeshRefKeys[MeshIndex]
, TEXT("We don't expect receiving null ISMCs at this point"));
Range.StaticMeshRefs.Add(StaticMeshRefKeys[MeshIndex]);
}
}
}
}
}
void UMassVisualizationComponent::ClearAllVisualInstances()
{
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
for (int32 SharedDataIndex = 0; SharedDataIndex < ISMCSharedData.Num(); ++SharedDataIndex)
{
if (UInstancedStaticMeshComponent* InstancedStaticMeshComponent = ISMCSharedData.GetAtIndex(SharedDataIndex).GetMutableISMComponent())
{
InstancedStaticMeshComponent->ClearInstances();
InstancedStaticMeshComponent->DestroyComponent();
}
}
MeshDescToISMCMap.Reset();
ISMCSharedData.Reset();
InstancedSMComponentsRequiringConstructing.Reset();
InstancedStaticMeshInfos.Reset();
}
void UMassVisualizationComponent::DirtyVisuals()
{
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
for (FMassInstancedStaticMeshInfo& Info : InstancedStaticMeshInfos)
{
for (UInstancedStaticMeshComponent* InstancedStaticMeshComponent : Info.InstancedStaticMeshComponents)
{
InstancedStaticMeshComponent->MarkRenderStateDirty();
}
}
}
void UMassVisualizationComponent::BeginVisualChanges()
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("MassVisualizationComponent BeginVisualChanges")
// Conditionally construct static mesh components
if (InstancedSMComponentsRequiringConstructing.Num())
{
ConstructStaticMeshComponents();
InstancedSMComponentsRequiringConstructing.Reset();
}
}
void UMassVisualizationComponent::HandleChangesWithExternalIDTracking(UInstancedStaticMeshComponent& ISMComponent, FMassISMCSharedData& SharedData)
{
if (SharedData.HasUpdatesToApply() == false)
{
// nothing to do here. We most probably were called as the part of the very first tick of this given SharedData
// since all the SharedData starts off as `dirty`.
return;
}
SCOPE_CYCLE_COUNTER(STAT_Mass_VisualizationComponent_HandleChangesWithExternalIDTracking);
// removing instances first, since this operation is more resilient to duplicates. Plus we make an arbitrary decision
// that it's better to have redundant things visible than not seeing required things
ProcessRemoves(ISMComponent, SharedData, /*bUpdateNavigation=*/false);
// NOTE: This code path is designed to only perform Adds, never updates so updates are filtered out along with duplicates.
TArray<FMassEntityHandle>& EntityHandles = SharedData.UpdateInstanceIds;
if (!EntityHandles.IsEmpty())
{
INC_DWORD_STAT_BY(STAT_Mass_VisualizationComponent_InstancesAddedNum, EntityHandles.Num());
FMassISMCSharedData::FEntityToPrimitiveIdMap& SharedIdMap = SharedData.GetMutableEntityPrimitiveToIdMap();
TArray<Experimental::FHashElementId> ElementIds;
ElementIds.SetNumUninitialized(EntityHandles.Num());
// Filter out all updates & duplicate adds
for (int32 IDIndex = EntityHandles.Num() - 1; IDIndex >= 0; --IDIndex)
{
bool bWasAlreadyInMap = false;
Experimental::FHashElementId ElementId = SharedIdMap.FindOrAddId(EntityHandles[IDIndex], FPrimitiveInstanceId{INDEX_NONE}, bWasAlreadyInMap);
if (bWasAlreadyInMap)
{
SharedData.RemoveUpdatedInstanceIdsAtSwap(IDIndex);
ElementIds.RemoveAtSwap(IDIndex);
}
else
{
ElementIds[IDIndex] = ElementId;
}
}
// it's possible the loop above removed all the data, so we do one last check
if (!EntityHandles.IsEmpty())
{
check(ElementIds.Num() == EntityHandles.Num());
const TArray<FTransform>& InstanceTransforms = SharedData.GetStaticMeshInstanceTransformsArray();
TConstArrayView<float> CustomFloatData = SharedData.GetStaticMeshInstanceCustomFloats();
const int32 NumCustomDataFloatsPerInstance = CustomFloatData.Num() / EntityHandles.Num();
// if these are the first entities we're adding we need to set NumCustomDataFloats so that the PerInstanceSMCustomData
// gets populated properly by the AddInstancesById call below
const int32 StartingCount = ISMComponent.GetNumInstances();
const bool bInitiallyEmpty = (StartingCount == 0);
ensure(bInitiallyEmpty || ISMComponent.NumCustomDataFloats == NumCustomDataFloatsPerInstance);
if (bInitiallyEmpty && NumCustomDataFloatsPerInstance > 0 && ISMComponent.Mobility != EComponentMobility::Static)
{
ISMComponent.SetNumCustomDataFloats(NumCustomDataFloatsPerInstance);
}
check(EntityHandles.Num() == InstanceTransforms.Num());
TArray<FPrimitiveInstanceId> NewIds = ISMComponent.AddInstancesById(InstanceTransforms, /*bWorldSpace=*/true, /*bUpdateNavigation =*/bInitiallyEmpty);
check(EntityHandles.Num() == NewIds.Num());
int32 EntityHandleToUpdateIndex = StartingCount;
for (int32 EntityHandleIndex = 0; EntityHandleIndex < EntityHandles.Num(); ++EntityHandleIndex, ++EntityHandleToUpdateIndex)
{
SharedIdMap.GetByElementId(ElementIds[EntityHandleIndex]).Value = NewIds[EntityHandleIndex];
}
ISMComponent.SetCustomData(StartingCount, StartingCount + EntityHandles.Num() - 1, CustomFloatData);
}
}
if (bNavigationRelevant && ISMComponent.GetInstanceCount() == 0)
{
FNavigationSystem::UnregisterComponent(ISMComponent);
}
}
void UMassVisualizationComponent::ProcessRemoves(UInstancedStaticMeshComponent& ISMComponent, FMassISMCSharedData& SharedData, const bool bUpdateNavigation /*= true*/)
{
if (!SharedData.GetRemoveInstanceIds().IsEmpty())
{
FMassISMCSharedData::FEntityToPrimitiveIdMap& SharedIdMap = SharedData.GetMutableEntityPrimitiveToIdMap();
INC_DWORD_STAT_BY(STAT_Mass_VisualizationComponent_InstancesRemovedNum, SharedData.GetRemoveInstanceIds().Num());
TConstArrayView<FMassEntityHandle> EntityHandles = SharedData.GetRemoveInstanceIds();
TArray<FPrimitiveInstanceId> ISMInstanceIds;
ISMInstanceIds.Reserve(EntityHandles.Num());
// Translate Mass IDs to ISMC IDs
for (const FMassEntityHandle MassInstanceId : EntityHandles)
{
Experimental::FHashElementId ElementId = SharedIdMap.FindId(MassInstanceId);
if (ElementId.IsValid())
{
FPrimitiveInstanceId InstanceId = SharedIdMap.GetByElementId(ElementId).Value;
check(InstanceId.IsValid());
SharedIdMap.RemoveByElementId(ElementId);
ISMInstanceIds.Add(InstanceId);
}
}
ISMComponent.RemoveInstancesById(ISMInstanceIds, bUpdateNavigation);
}
}
void UMassVisualizationComponent::EndVisualChanges()
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("MassVisualizationComponent EndVisualChanges")
SCOPE_CYCLE_COUNTER(STAT_Mass_VisualizationComponent_EndVisualChanges);
#if STATS
if (UE::Mass::Representation::LastStatsResetFrame != GFrameNumber)
{
SET_DWORD_STAT(STAT_Mass_VisualizationComponent_InstancesRemovedNum, 0);
SET_DWORD_STAT(STAT_Mass_VisualizationComponent_InstancesAddedNum, 0);
UE::Mass::Representation::LastStatsResetFrame = GFrameNumber;
}
#endif // STATS
// Batch update gathered instance transforms
for (FMassISMCSharedDataMap::FDirtyIterator It(ISMCSharedData); It; ++It)
{
FMassISMCSharedData& SharedData = *It;
UInstancedStaticMeshComponent* ISMComponent = SharedData.GetMutableISMComponent();
// @todo need to check validity this way since Mass used to rely on the assumption that all the ISM components used were
// under its control. That's no longer the case, but the system has not been updated to take that into consideration.
// This is a temporary fix.
if (IsValid(ISMComponent))
{
ensureMsgf(!Cast<UHierarchicalInstancedStaticMeshComponent>(ISMComponent), TEXT("The UMassVisualizationComponent does not support driving a HISM, since it is not suitable for rapid updates, replace `%s`."), *ISMComponent->GetFullName());
if (SharedData.RequiresExternalInstanceIDTracking())
{
HandleChangesWithExternalIDTracking(*ISMComponent, SharedData);
It.ClearDirtyFlag();
}
else
{
// Process all removes.
ProcessRemoves(*ISMComponent, SharedData);
const int32 NumCustomDataFloats = SharedData.StaticMeshInstanceCustomFloats.Num() / (FMath::Max(1, SharedData.UpdateInstanceIds.Num()));
// Ensure InstanceCustomData is passed if NumCustomDataFloats > 0. If it is, also make sure
// its length is NumCustomDataFloats * InstanceTransforms.Num()
ensure(NumCustomDataFloats == 0 || (SharedData.StaticMeshInstanceCustomFloats.Num() == NumCustomDataFloats * SharedData.UpdateInstanceIds.Num()));
ISMComponent->SetNumCustomDataFloats(NumCustomDataFloats);
TArray<FMassEntityHandle>& EntityHandles = SharedData.UpdateInstanceIds;
{
// Loop over all the instances in the update and
// 1. Sort the data such that all Adds are last
// 2. Remove any duplicates (unsure if they may exist)
FMassISMCSharedData::FEntityToPrimitiveIdMap& SharedIdMap = SharedData.GetMutableEntityPrimitiveToIdMap();
// Filter out all updates & duplicate adds
TBitArray<> Unprocessed;
Unprocessed.SetNum(SharedIdMap.GetMaxIndex(), true);
// Process interval
TConstArrayView<FTransform> PrevInstanceTransforms = SharedData.GetStaticMeshInstancePrevTransforms();
TConstArrayView<FTransform> InstanceTransforms = SharedData.GetStaticMeshInstanceTransformsArray();
TConstArrayView<float> CustomDataFloats = SharedData.GetStaticMeshInstanceCustomFloats();
// Enable support for per-instance prev transforms, if it was not already enabled it will copy the current transforms.
ISMComponent->SetHasPerInstancePrevTransforms(!PrevInstanceTransforms.IsEmpty());
struct FAddItem
{
Experimental::FHashElementId ElementId;
int32 IDIndex;
};
TArray<FAddItem> ToAdd;
ToAdd.Reserve(EntityHandles.Num());
for (int32 IDIndex = 0; IDIndex < EntityHandles.Num(); ++IDIndex)
{
bool bWasAlreadyInMap = false;
Experimental::FHashElementId ElementId = SharedIdMap.FindOrAddId(EntityHandles[IDIndex], FPrimitiveInstanceId{INDEX_NONE}, bWasAlreadyInMap);
// if it was already in the map, it may be a duplicate if we have processed it already
bool bIsDuplicate = bWasAlreadyInMap && !Unprocessed[ElementId.GetIndex()];
if (bIsDuplicate)
{
continue;
}
FPrimitiveInstanceId Id = SharedIdMap.GetByElementId(ElementId).Value;
if (!Id.IsValid())
{
check(!bWasAlreadyInMap);
ToAdd.Emplace(FAddItem{ElementId, IDIndex});
}
else
{
ISMComponent->UpdateInstanceTransformById(Id, InstanceTransforms[IDIndex]);
if (!PrevInstanceTransforms.IsEmpty())
{
ISMComponent->SetPreviousTransformById(Id, PrevInstanceTransforms[IDIndex]);
}
if (!CustomDataFloats.IsEmpty())
{
ISMComponent->SetCustomDataById(Id, MakeArrayView(CustomDataFloats.GetData() + IDIndex * NumCustomDataFloats, NumCustomDataFloats));
}
}
// Make sure we have enough space to track the already processed IDs
Unprocessed.SetNum(SharedIdMap.GetMaxIndex(), true);
Unprocessed[ElementId.GetIndex()] = false;
}
// Collect unwanted items & remove
TArray<FPrimitiveInstanceId> RemovedISMInstanceIds;
RemovedISMInstanceIds.Reserve(Unprocessed.Num());
{
for(TConstSetBitIterator<> BitIt(Unprocessed); BitIt; ++BitIt)
{
Experimental::FHashElementId ElementId(BitIt.GetIndex());
if (SharedIdMap.ContainsElementId(ElementId))
{
FPrimitiveInstanceId InstanceId = SharedIdMap.GetByElementId(ElementId).Value;
check(InstanceId.IsValid());
SharedIdMap.RemoveByElementId(ElementId);
RemovedISMInstanceIds.Add(InstanceId);
}
}
ISMComponent->RemoveInstancesById(RemovedISMInstanceIds);
}
// Process deferred adds.
for (FAddItem AddItem : ToAdd)
{
FPrimitiveInstanceId Id = ISMComponent->AddInstanceById(InstanceTransforms[AddItem.IDIndex]);
check(!SharedIdMap.GetByElementId(AddItem.ElementId).Value.IsValid());
SharedIdMap.GetByElementId(AddItem.ElementId).Value = Id;
if (!PrevInstanceTransforms.IsEmpty())
{
ISMComponent->SetPreviousTransformById(Id, PrevInstanceTransforms[AddItem.IDIndex]);
}
if (!CustomDataFloats.IsEmpty())
{
ISMComponent->SetCustomDataById(Id, MakeArrayView(CustomDataFloats.GetData() + AddItem.IDIndex * NumCustomDataFloats, NumCustomDataFloats));
}
}
// note that we're not clearing the dirty flag on purpose - these components require constant updates
}
}
// bump the touch counter so that anyone caching data based on contents of this SharedData can detect the change
++SharedData.ComponentInstanceIdTouchCounter;
}
SharedData.ResetAccumulatedData();
}
}
//---------------------------------------------------------------
// FMassLODSignificanceRange
//---------------------------------------------------------------
void FMassLODSignificanceRange::AddBatchedTransform(const FMassEntityHandle EntityHandle, const FTransform& Transform, const FTransform& PrevTransform, TConstArrayView<FISMCSharedDataKey> ExcludeStaticMeshRefs)
{
check(ISMCSharedDataPtr);
for (int32 StaticMeshIndex = 0; StaticMeshIndex < StaticMeshRefs.Num(); ++StaticMeshIndex)
{
if (ExcludeStaticMeshRefs.Contains(StaticMeshRefs[StaticMeshIndex]))
{
continue;
}
if (FMassISMCSharedData* SharedData = ISMCSharedDataPtr->GetAndMarkDirty(StaticMeshRefs[StaticMeshIndex]))
{
SharedData->UpdateInstanceIds.Add(EntityHandle);
FTransform AdjustedTransform{SharedData->LocalTransform * Transform};
SharedData->StaticMeshInstanceTransforms.Add(AdjustedTransform);
SharedData->StaticMeshInstancePrevTransforms.Add(PrevTransform);
}
}
}
void FMassLODSignificanceRange::AddBatchedCustomDataFloats(const TArray<float>& CustomFloats, const TArray<FISMCSharedDataKey>& ExcludeStaticMeshRefs)
{
check(ISMCSharedDataPtr);
for (int32 StaticMeshIndex = 0; StaticMeshIndex < StaticMeshRefs.Num(); ++StaticMeshIndex)
{
if (ExcludeStaticMeshRefs.Contains(StaticMeshRefs[StaticMeshIndex]))
{
continue;
}
if (FMassISMCSharedData* SharedData = ISMCSharedDataPtr->GetAndMarkDirty(StaticMeshRefs[StaticMeshIndex]))
{
SharedData->StaticMeshInstanceCustomFloats.Append(CustomFloats);
}
}
}
void FMassLODSignificanceRange::AddInstance(const FMassEntityHandle EntityHandle, const FTransform& Transform)
{
check(ISMCSharedDataPtr);
for (int32 StaticMeshIndex = 0; StaticMeshIndex < StaticMeshRefs.Num(); ++StaticMeshIndex)
{
if (FMassISMCSharedData* SharedData = ISMCSharedDataPtr->GetAndMarkDirty(StaticMeshRefs[StaticMeshIndex]))
{
SharedData->UpdateInstanceIds.Add(EntityHandle);
FTransform AdjustedTransform{SharedData->LocalTransform * Transform};
SharedData->StaticMeshInstanceTransforms.Add(AdjustedTransform);
SharedData->StaticMeshInstancePrevTransforms.Add(AdjustedTransform);
}
}
}
void FMassLODSignificanceRange::RemoveInstance(const FMassEntityHandle EntityHandle)
{
check(ISMCSharedDataPtr);
for (int32 StaticMeshIndex = 0; StaticMeshIndex < StaticMeshRefs.Num(); ++StaticMeshIndex)
{
if (FMassISMCSharedData* SharedData = ISMCSharedDataPtr->GetAndMarkDirty(StaticMeshRefs[StaticMeshIndex]))
{
SharedData->RemoveInstanceIds.Add(EntityHandle);
}
}
}
void FMassLODSignificanceRange::WriteCustomDataFloatsAtStartIndex(int32 StaticMeshIndex, const TArrayView<float>& CustomFloats, const int32 FloatsPerInstance, const int32 StartFloatIndex, const TArray<FISMCSharedDataKey>& ExcludeStaticMeshRefs)
{
check(ISMCSharedDataPtr);
if (StaticMeshRefs.IsValidIndex(StaticMeshIndex))
{
if (ExcludeStaticMeshRefs.Contains(StaticMeshRefs[StaticMeshIndex]))
{
return;
}
if (FMassISMCSharedData* SharedData = ISMCSharedDataPtr->GetAndMarkDirty(StaticMeshRefs[StaticMeshIndex]))
{
const int32 StartIndex = FloatsPerInstance * SharedData->WriteIterator + StartFloatIndex;
ensure(SharedData->StaticMeshInstanceCustomFloats.Num() >= StartIndex + CustomFloats.Num());
for (int CustomFloatIdx = 0; CustomFloatIdx < CustomFloats.Num(); CustomFloatIdx++)
{
SharedData->StaticMeshInstanceCustomFloats[StartIndex + CustomFloatIdx] = CustomFloats[CustomFloatIdx];
}
SharedData->WriteIterator++;
}
}
}
//-----------------------------------------------------------------------------
// DEPRECATED
//-----------------------------------------------------------------------------
void UMassVisualizationComponent::RemoveISMComponent(UInstancedStaticMeshComponent& ISMComponent)
{
UE_MT_SCOPED_WRITE_ACCESS(InstancedStaticMeshInfosDetector);
const FStaticMeshInstanceVisualizationDescHandle* VisualDescHandlePtr = ISMComponentMap.Find(&ISMComponent);
if (VisualDescHandlePtr)
{
RemoveVisualDesc(*VisualDescHandlePtr);
}
}
void UMassVisualizationComponent::RemoveVisualDescByIndex(const int32 VisualizationIndex)
{
RemoveVisualDesc(FStaticMeshInstanceVisualizationDescHandle(VisualizationIndex));
}