mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-22 02:06:03 +08:00
2134 lines
62 KiB
C++
2134 lines
62 KiB
C++
// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
|
|
|
|
#include "RuntimeMeshComponentPluginPrivatePCH.h"
|
|
#include "RuntimeMeshComponent.h"
|
|
#include "RuntimeMeshCore.h"
|
|
#include "RuntimeMeshGenericVertex.h"
|
|
#include "RuntimeMeshVersion.h"
|
|
#include "PhysicsEngine/PhysicsSettings.h"
|
|
|
|
|
|
/** Runtime mesh scene proxy */
|
|
class FRuntimeMeshSceneProxy : public FPrimitiveSceneProxy
|
|
{
|
|
private:
|
|
// Temporarily holds all section creation data until this proxy is passsed to the RT.
|
|
// After this data is applied this array is cleared.
|
|
TArray<FRuntimeMeshSectionCreateDataInterface*> SectionCreationData;
|
|
|
|
public:
|
|
|
|
FRuntimeMeshSceneProxy(URuntimeMeshComponent* Component)
|
|
: FPrimitiveSceneProxy(Component)
|
|
, BodySetup(Component->GetBodySetup())
|
|
{
|
|
bStaticElementsAlwaysUseProxyPrimitiveUniformBuffer = true;
|
|
|
|
|
|
// Get the proxy for all mesh sections
|
|
|
|
const int32 NumSections = Component->MeshSections.Num();
|
|
Sections.AddDefaulted(NumSections);
|
|
|
|
for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
|
|
{
|
|
RuntimeMeshSectionPtr& SourceSection = Component->MeshSections[SectionIdx];
|
|
if (SourceSection.IsValid())
|
|
{
|
|
UMaterialInterface* Material = Component->GetMaterial(SectionIdx);
|
|
if (Material == nullptr)
|
|
{
|
|
Material = UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
|
|
// Get the section creation data
|
|
auto* SectionData = SourceSection->GetSectionCreationData(&GetScene(), Material);
|
|
SectionData->SetTargetSection(SectionIdx);
|
|
SectionCreationData.Add(SectionData);
|
|
|
|
}
|
|
}
|
|
|
|
// Update material relevancy information needed to control the rendering.
|
|
UpdateMaterialRelevance();
|
|
}
|
|
|
|
virtual ~FRuntimeMeshSceneProxy()
|
|
{
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
delete Section;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateRenderThreadResources() override
|
|
{
|
|
FPrimitiveSceneProxy::CreateRenderThreadResources();
|
|
|
|
for (auto Section : SectionCreationData)
|
|
{
|
|
CreateSection_RenderThread(Section);
|
|
}
|
|
// The individual items are deleted by CreateSection_RenderThread so just clear the array.
|
|
SectionCreationData.Empty();
|
|
}
|
|
|
|
/** Called on render thread to create a new dynamic section. (Static sections are handled differently) */
|
|
void CreateSection_RenderThread(FRuntimeMeshSectionCreateDataInterface* SectionData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateSection_RenderThread);
|
|
|
|
check(IsInRenderingThread());
|
|
check(SectionData);
|
|
|
|
int32 SectionIndex = SectionData->GetTargetSection();
|
|
|
|
// Make sure the array is big enough
|
|
if (SectionIndex >= Sections.Num())
|
|
{
|
|
Sections.SetNum(SectionIndex + 1, false);
|
|
}
|
|
|
|
// If a section already exists... destroy it!
|
|
if (FRuntimeMeshSectionProxyInterface* Section = Sections[SectionIndex])
|
|
{
|
|
delete Section;
|
|
}
|
|
|
|
// Get the proxy and finish the creation here on the render thread.
|
|
FRuntimeMeshSectionProxyInterface* Section = SectionData->NewProxy;
|
|
Section->FinishCreate_RenderThread(SectionData);
|
|
|
|
// Save ref to new section
|
|
Sections[SectionIndex] = Section;
|
|
|
|
delete SectionData;
|
|
|
|
// Update material relevancy information needed to control the rendering.
|
|
UpdateMaterialRelevance();
|
|
}
|
|
|
|
/** Called on render thread to assign new dynamic data */
|
|
void UpdateSection_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSection_RenderThread);
|
|
|
|
check(IsInRenderingThread());
|
|
check(SectionData);
|
|
|
|
if (SectionData->GetTargetSection() < Sections.Num() && Sections[SectionData->GetTargetSection()] != nullptr)
|
|
{
|
|
Sections[SectionData->GetTargetSection()]->FinishUpdate_RenderThread(SectionData);
|
|
}
|
|
|
|
delete SectionData;
|
|
}
|
|
|
|
void UpdateSectionPositionOnly_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSectionPositionOnly_RenderThread);
|
|
|
|
check(IsInRenderingThread());
|
|
check(SectionData);
|
|
|
|
if (SectionData->GetTargetSection() < Sections.Num() && Sections[SectionData->GetTargetSection()] != nullptr)
|
|
{
|
|
Sections[SectionData->GetTargetSection()]->FinishPositionUpdate_RenderThread(SectionData);
|
|
}
|
|
|
|
delete SectionData;
|
|
}
|
|
|
|
void UpdateSectionProperties_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSectionProperties_RenderThread);
|
|
|
|
check(IsInRenderingThread());
|
|
check(SectionData);
|
|
|
|
int32 SectionIndex = SectionData->GetTargetSection();
|
|
|
|
if (SectionIndex < Sections.Num() && Sections[SectionIndex] != nullptr)
|
|
{
|
|
Sections[SectionIndex]->FinishPropertyUpdate_RenderThread(SectionData);
|
|
}
|
|
}
|
|
|
|
|
|
void DestroySection_RenderThread(int32 SectionIndex)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
if (SectionIndex < Sections.Num() && Sections[SectionIndex] != nullptr)
|
|
{
|
|
delete Sections[SectionIndex];
|
|
Sections[SectionIndex] = nullptr;
|
|
}
|
|
|
|
// Update material relevancy information needed to control the rendering.
|
|
UpdateMaterialRelevance();
|
|
}
|
|
|
|
void ApplyBatchUpdate_RenderThread(FRuntimeMeshBatchUpdateData* BatchUpdateData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ApplyBatchUpdate_RenderThread);
|
|
|
|
check(IsInRenderingThread());
|
|
check(BatchUpdateData);
|
|
|
|
// Destroy flagged sections
|
|
for (auto& SectionIndex : BatchUpdateData->DestroySections)
|
|
{
|
|
DestroySection_RenderThread(SectionIndex);
|
|
}
|
|
|
|
// Create new sections
|
|
for (auto& SectionToCreate : BatchUpdateData->CreateSections)
|
|
{
|
|
CreateSection_RenderThread(static_cast<FRuntimeMeshSectionCreateDataInterface*>(SectionToCreate));
|
|
}
|
|
|
|
// Update sections
|
|
for (auto& SectionToUpdate : BatchUpdateData->UpdateSections)
|
|
{
|
|
UpdateSection_RenderThread(SectionToUpdate);
|
|
}
|
|
|
|
// Apply section property updates
|
|
for (auto& SectionToUpdate : BatchUpdateData->PropertyUpdateSections)
|
|
{
|
|
UpdateSectionProperties_RenderThread(SectionToUpdate);
|
|
}
|
|
|
|
delete BatchUpdateData;
|
|
|
|
}
|
|
|
|
|
|
bool HasDynamicSections() const
|
|
{
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section && !Section->WantsToRenderInStaticPath())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HasStaticSections() const
|
|
{
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section && Section->WantsToRenderInStaticPath())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
|
|
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
|
|
#else
|
|
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) override
|
|
#endif
|
|
{
|
|
FPrimitiveViewRelevance Result;
|
|
Result.bDrawRelevance = IsShown(View);
|
|
Result.bShadowRelevance = IsShadowCast(View);
|
|
|
|
bool bForceDynamicPath = IsRichView(*View->Family) || View->Family->EngineShowFlags.Wireframe || IsSelected() || !IsStaticPathAvailable();
|
|
Result.bStaticRelevance = !bForceDynamicPath && HasStaticSections();
|
|
Result.bDynamicRelevance = bForceDynamicPath || HasDynamicSections();
|
|
|
|
Result.bRenderInMainPass = ShouldRenderInMainPass();
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
|
|
Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
|
|
#endif
|
|
Result.bRenderCustomDepth = ShouldRenderCustomDepth();
|
|
MaterialRelevance.SetPrimitiveViewRelevance(Result);
|
|
return Result;
|
|
}
|
|
|
|
void CreateMeshBatch(FMeshBatch& MeshBatch, FRuntimeMeshSectionProxyInterface* Section, FMaterialRenderProxy* WireframeMaterial) const
|
|
{
|
|
Section->CreateMeshBatch(MeshBatch, WireframeMaterial, IsSelected());
|
|
|
|
MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();
|
|
MeshBatch.bCanApplyViewModeOverrides = true;
|
|
|
|
FMeshBatchElement& BatchElement = MeshBatch.Elements[0];
|
|
BatchElement.PrimitiveUniformBufferResource = &GetUniformBuffer();
|
|
}
|
|
|
|
virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_DrawStaticElements);
|
|
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section && Section->ShouldRender() && Section->WantsToRenderInStaticPath())
|
|
{
|
|
FMeshBatch MeshBatch;
|
|
CreateMeshBatch(MeshBatch, Section, nullptr);
|
|
PDI->DrawMesh(MeshBatch, FLT_MAX);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_GetDynamicMeshElements);
|
|
|
|
// Set up wireframe material (if needed)
|
|
const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
|
|
|
|
FColoredMaterialRenderProxy* WireframeMaterialInstance = nullptr;
|
|
if (bWireframe)
|
|
{
|
|
WireframeMaterialInstance = new FColoredMaterialRenderProxy(
|
|
GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : nullptr,
|
|
FLinearColor(0, 0.5f, 1.f)
|
|
);
|
|
|
|
Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
|
|
}
|
|
|
|
// Iterate over sections
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section && Section->ShouldRender())
|
|
{
|
|
// Add the mesh batch to every view it's visible in
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if (VisibilityMap & (1 << ViewIndex))
|
|
{
|
|
bool bForceDynamicPath = IsRichView(*Views[ViewIndex]->Family) || Views[ViewIndex]->Family->EngineShowFlags.Wireframe || IsSelected() || !IsStaticPathAvailable();
|
|
|
|
if (bForceDynamicPath || !Section->WantsToRenderInStaticPath())
|
|
{
|
|
FMeshBatch& MeshBatch = Collector.AllocateMesh();
|
|
CreateMeshBatch(MeshBatch, Section, WireframeMaterialInstance);
|
|
|
|
Collector.AddMesh(ViewIndex, MeshBatch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw bounds
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if (VisibilityMap & (1 << ViewIndex))
|
|
{
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
|
|
// Draw simple collision as wireframe if 'show collision', and collision is enabled, and we are not using the complex as the simple
|
|
if (ViewFamily.EngineShowFlags.Collision && IsCollisionEnabled() && BodySetup->GetCollisionTraceFlag() != ECollisionTraceFlag::CTF_UseComplexAsSimple)
|
|
{
|
|
FTransform GeomTransform(GetLocalToWorld());
|
|
BodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(FColor(157, 149, 223, 255), IsSelected(), IsHovered()).ToFColor(true), NULL, false, false, UseEditorDepthTest(), ViewIndex, Collector);
|
|
}
|
|
#endif
|
|
|
|
// Render bounds
|
|
RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
virtual bool CanBeOccluded() const override
|
|
{
|
|
return !MaterialRelevance.bDisableDepthTest;
|
|
}
|
|
|
|
virtual uint32 GetMemoryFootprint(void) const
|
|
{
|
|
return(sizeof(*this) + GetAllocatedSize());
|
|
}
|
|
|
|
uint32 GetAllocatedSize(void) const
|
|
{
|
|
return(FPrimitiveSceneProxy::GetAllocatedSize());
|
|
}
|
|
|
|
void UpdateMaterialRelevance()
|
|
{
|
|
FMaterialRelevance NewMaterialRelevance;
|
|
for (FRuntimeMeshSectionProxyInterface* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
NewMaterialRelevance |= Section->GetMaterialRelevance();
|
|
}
|
|
}
|
|
MaterialRelevance = NewMaterialRelevance;
|
|
}
|
|
|
|
private:
|
|
/** Array of sections */
|
|
TArray<FRuntimeMeshSectionProxyInterface*> Sections;
|
|
UBodySetup* BodySetup;
|
|
FMaterialRelevance MaterialRelevance;
|
|
};
|
|
|
|
|
|
|
|
|
|
void FRuntimeMeshComponentPrePhysicsTickFunction::ExecuteTick( float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
|
|
{
|
|
/* Ensure target still exists */
|
|
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
|
|
bool bIsValid = Target && !Target->IsPendingKillOrUnreachable();
|
|
#else
|
|
bool bIsValid = Target && !Target->HasAnyFlags(RF_PendingKill | RF_Unreachable);
|
|
#endif
|
|
|
|
if (bIsValid)
|
|
{
|
|
FScopeCycleCounterUObject ActorScope(Target);
|
|
Target->BakeCollision();
|
|
}
|
|
}
|
|
|
|
FString FRuntimeMeshComponentPrePhysicsTickFunction::DiagnosticMessage()
|
|
{
|
|
return Target->GetFullName() + TEXT("[PrePhysicsTick]");
|
|
}
|
|
|
|
|
|
|
|
/* Helper for converting an array of FLinearColor to an array of FColors*/
|
|
void ConvertLinearColorToFColor(const TArray<FLinearColor>& LinearColors, TArray<FColor>& Colors)
|
|
{
|
|
Colors.SetNumUninitialized(LinearColors.Num());
|
|
for (int32 Index = 0; Index < LinearColors.Num(); Index++)
|
|
{
|
|
Colors[Index] = LinearColors[Index].ToFColor(false);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
URuntimeMeshComponent::URuntimeMeshComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, bUseComplexAsSimpleCollision(true)
|
|
, bShouldSerializeMeshData(true)
|
|
, bCollisionDirty(true)
|
|
, CollisionMode(ERuntimeMeshCollisionCookingMode::CookingPerformance)
|
|
{
|
|
// Setup the collision update ticker
|
|
PrePhysicsTick.TickGroup = TG_PrePhysics;
|
|
PrePhysicsTick.bCanEverTick = true;
|
|
PrePhysicsTick.bStartWithTickEnabled = true;
|
|
|
|
// Reset the batch state
|
|
BatchState.ResetBatch();
|
|
|
|
SetNetAddressable();
|
|
}
|
|
|
|
TSharedPtr<FRuntimeMeshSectionInterface> URuntimeMeshComponent::CreateOrResetSectionLegacyType(int32 SectionIndex, int32 NumUVChannels)
|
|
{
|
|
if (NumUVChannels == 1)
|
|
{
|
|
return CreateOrResetSection<FRuntimeMeshSection<FRuntimeMeshVertexSimple>>(SectionIndex, false, true);
|
|
}
|
|
else if (NumUVChannels == 2)
|
|
{
|
|
return CreateOrResetSection<FRuntimeMeshSection<FRuntimeMeshVertexDualUV>>(SectionIndex, false, true);
|
|
}
|
|
else
|
|
{
|
|
check(false && "Legacy sections only support standard vertex formats wit 1 or 2 uv channels");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::CreateSectionInternal(int32 SectionIndex, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
|
|
check(Section.IsValid());
|
|
|
|
// Update normal/tangents if requested...
|
|
if (!!(UpdateFlags & ESectionUpdateFlags::CalculateNormalTangent))
|
|
{
|
|
Section->GenerateNormalTangent();
|
|
}
|
|
|
|
// calculate tessellation if requested...
|
|
if (!!(UpdateFlags & ESectionUpdateFlags::CalculateTessellationIndices))
|
|
{
|
|
Section->GenerateTessellationIndices();
|
|
}
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark section created
|
|
BatchState.MarkSectionCreated(SectionIndex, Section->UpdateFrequency == EUpdateFrequency::Infrequent);
|
|
|
|
// Flag collision if this section affects it
|
|
if (Section->CollisionEnabled)
|
|
{
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
|
|
// Flag bounds update
|
|
BatchState.MarkBoundsDirty();
|
|
|
|
// bail since we don't update directly in this case.
|
|
return;
|
|
}
|
|
|
|
// Enqueue the RT command if we already have a SceneProxy
|
|
if (SceneProxy && Section->UpdateFrequency != EUpdateFrequency::Infrequent)
|
|
{
|
|
// Gather all needed update info
|
|
auto* SectionData = Section->GetSectionCreationData(GetScene(), GetSectionMaterial(SectionIndex));
|
|
SectionData->SetTargetSection(SectionIndex);
|
|
|
|
// Enqueue update on RT
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshSectionCreate,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
FRuntimeMeshSectionCreateDataInterface*, SectionData, SectionData,
|
|
{
|
|
RuntimeMeshSceneProxy->CreateSection_RenderThread(SectionData);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Mark the render state dirty so it's recreated when necessary.
|
|
MarkRenderStateDirty();
|
|
}
|
|
|
|
// Mark collision dirty so it's re-baked at the end of this frame
|
|
if (Section->CollisionEnabled)
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
|
|
// Update overall bounds
|
|
UpdateLocalBounds();
|
|
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateSectionInternal(int32 SectionIndex, bool bHadVertexPositionsUpdate, bool bHadVertexUpdates, bool bHadIndexUpdates, bool bNeedsBoundsUpdate, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
// Ensure that something was updated
|
|
check(bHadVertexPositionsUpdate || bHadVertexUpdates || bHadIndexUpdates || bNeedsBoundsUpdate);
|
|
|
|
check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());
|
|
RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
|
|
|
|
// Update normal/tangents if requested...
|
|
if (!!(UpdateFlags & ESectionUpdateFlags::CalculateNormalTangent))
|
|
{
|
|
Section->GenerateNormalTangent();
|
|
}
|
|
|
|
// calculate tessellation if requested...
|
|
if (!!(UpdateFlags & ESectionUpdateFlags::CalculateTessellationIndices))
|
|
{
|
|
Section->GenerateTessellationIndices();
|
|
}
|
|
|
|
/* Make sure this is only flagged if the section is dual buffer */
|
|
bHadVertexPositionsUpdate = Section->IsDualBufferSection() && bHadVertexPositionsUpdate;
|
|
bool bNeedsCollisionUpdate = Section->CollisionEnabled && (bHadVertexPositionsUpdate || (!Section->IsDualBufferSection() && bHadVertexUpdates));
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark update for section or promote to proxy recreate if static section
|
|
if (Section->UpdateFrequency == EUpdateFrequency::Infrequent)
|
|
{
|
|
BatchState.MarkRenderStateDirty();
|
|
}
|
|
else
|
|
{
|
|
ERuntimeMeshSectionBatchUpdateType UpdateType = ERuntimeMeshSectionBatchUpdateType::None;
|
|
UpdateType |= bHadVertexPositionsUpdate ? ERuntimeMeshSectionBatchUpdateType::PositionsUpdate : ERuntimeMeshSectionBatchUpdateType::None;
|
|
UpdateType |= bHadVertexUpdates ? ERuntimeMeshSectionBatchUpdateType::VerticesUpdate : ERuntimeMeshSectionBatchUpdateType::None;
|
|
UpdateType |= bHadIndexUpdates ? ERuntimeMeshSectionBatchUpdateType::IndicesUpdate : ERuntimeMeshSectionBatchUpdateType::None;
|
|
|
|
BatchState.MarkUpdateForSection(SectionIndex, UpdateType);
|
|
}
|
|
|
|
// Flag collision if this section affects it
|
|
if (bNeedsCollisionUpdate)
|
|
{
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
|
|
// Flag bounds update if needed.
|
|
if (bNeedsBoundsUpdate)
|
|
{
|
|
BatchState.MarkBoundsDirty();
|
|
}
|
|
|
|
// bail since we don't update directly in this case.
|
|
return;
|
|
}
|
|
|
|
|
|
// Send the update to the render thread if the scene proxy exists
|
|
if (SceneProxy && Section->UpdateFrequency != EUpdateFrequency::Infrequent)
|
|
{
|
|
auto* SectionData = Section->GetSectionUpdateData(bHadVertexPositionsUpdate, bHadVertexUpdates, bHadIndexUpdates);
|
|
SectionData->SetTargetSection(SectionIndex);
|
|
|
|
// Enqueue update on RT
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshSectionUpdate,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
|
|
{
|
|
RuntimeMeshSceneProxy->UpdateSection_RenderThread(SectionData);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Mark the renderstate dirty so it's recreated when necessary.
|
|
MarkRenderStateDirty();
|
|
}
|
|
|
|
// Mark collision dirty so it's re-baked at the end of this frame
|
|
if (bNeedsCollisionUpdate)
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
|
|
// Update overall bounds if needed
|
|
if (bNeedsBoundsUpdate)
|
|
{
|
|
UpdateLocalBounds();
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateSectionVertexPositionsInternal(int32 SectionIndex, bool bNeedsBoundsUpdate)
|
|
{
|
|
check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());
|
|
RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
|
|
|
|
if (SceneProxy)
|
|
{
|
|
auto SectionData = Section->GetSectionPositionUpdateData();
|
|
SectionData->SetTargetSection(SectionIndex);
|
|
|
|
// Enqueue command to modify render thread info
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshSectionPositionUpdate,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
|
|
{
|
|
RuntimeMeshSceneProxy->UpdateSectionPositionOnly_RenderThread(SectionData);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MarkRenderStateDirty();
|
|
}
|
|
|
|
if (bNeedsBoundsUpdate)
|
|
{
|
|
UpdateLocalBounds();
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateSectionPropertiesInternal(int32 SectionIndex, bool bUpdateRequiresProxyRecreateIfStatic)
|
|
{
|
|
check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());
|
|
RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
|
|
|
|
bool bRequiresRecreate = bUpdateRequiresProxyRecreateIfStatic && Section->UpdateFrequency == EUpdateFrequency::Infrequent;
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
if (bRequiresRecreate)
|
|
{
|
|
BatchState.MarkRenderStateDirty();
|
|
}
|
|
else
|
|
{
|
|
BatchState.MarkUpdateForSection(SectionIndex, ERuntimeMeshSectionBatchUpdateType::PropertyUpdate);
|
|
}
|
|
|
|
// bail since we don't update directly in this case.
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
if (SceneProxy && !bRequiresRecreate)
|
|
{
|
|
auto SectionData = new FRuntimeMeshSectionPropertyUpdateData();
|
|
SectionData->SetTargetSection(SectionIndex);
|
|
SectionData->bIsVisible = Section->bIsVisible;
|
|
SectionData->bCastsShadow = Section->bCastsShadow;
|
|
|
|
|
|
// Enqueue command to modify render thread info
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshSectionPropertyUpdate,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
|
|
{
|
|
RuntimeMeshSceneProxy->UpdateSectionProperties_RenderThread(SectionData);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MarkRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate);
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Check dual buffer section status
|
|
if (VertexPositions.Num() != Section->PositionVertexBuffer.Num())
|
|
{
|
|
Log(TEXT("UpdateMeshSection() - Positions cannot change length unless the vertexdata is updated as well."), true);
|
|
return;
|
|
}
|
|
|
|
bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
|
|
bool bNeedsBoundsUpdate = false;
|
|
|
|
// Update vertex positions if supplied
|
|
bool bUpdatedVertexPositions = false;
|
|
if (VertexPositions.Num() > 0)
|
|
{
|
|
bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, nullptr, bShouldUseMove);
|
|
bUpdatedVertexPositions = true;
|
|
}
|
|
else
|
|
{
|
|
Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
|
|
}
|
|
|
|
// Finalize section update if we have anything to apply
|
|
if (bUpdatedVertexPositions)
|
|
{
|
|
UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundsUpdate);
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate_WithBoundinBox);
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Check dual buffer section status
|
|
if (VertexPositions.Num() != Section->PositionVertexBuffer.Num())
|
|
{
|
|
Log(TEXT("UpdateMeshSection() - Positions cannot change length unless the vertexdata is updated as well."), true);
|
|
return;
|
|
}
|
|
|
|
bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
|
|
bool bNeedsBoundsUpdate = false;
|
|
|
|
// Update vertex positions if supplied
|
|
bool bUpdatedVertexPositions = false;
|
|
if (VertexPositions.Num() > 0)
|
|
{
|
|
bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, &BoundingBox, bShouldUseMove);
|
|
bUpdatedVertexPositions = true;
|
|
}
|
|
else
|
|
{
|
|
Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
|
|
}
|
|
|
|
// Finalize section update if we have anything to apply
|
|
if (bUpdatedVertexPositions)
|
|
{
|
|
UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundsUpdate);
|
|
}
|
|
}
|
|
|
|
|
|
TArray<FVector>* URuntimeMeshComponent::BeginMeshSectionPositionUpdate(int32 SectionIndex)
|
|
{
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, nullptr);
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
return &Section->PositionVertexBuffer;
|
|
}
|
|
|
|
void URuntimeMeshComponent::EndMeshSectionPositionUpdate(int32 SectionIndex)
|
|
{
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
|
|
|
|
// TODO: Validate that the position buffer is still the same length
|
|
|
|
UpdateSectionVertexPositionsInternal(SectionIndex, true);
|
|
}
|
|
|
|
void URuntimeMeshComponent::EndMeshSectionPositionUpdate(int32 SectionIndex, const FBox& BoundingBox)
|
|
{
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
|
|
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
bool bNeedsBoundingBoxUpdate = !(Section->LocalBoundingBox == BoundingBox);
|
|
if (bNeedsBoundingBoxUpdate)
|
|
{
|
|
Section->LocalBoundingBox = BoundingBox;
|
|
}
|
|
|
|
// TODO: Validate that the position buffer is still the same length
|
|
|
|
UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundingBoxUpdate);
|
|
}
|
|
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
// Bail if we have no buffers to update.
|
|
if (UpdatedBuffers == ERuntimeMeshBuffer::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section and update bounding box
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
Section->RecalculateBoundingBox();
|
|
|
|
// Finalize section update
|
|
UpdateSectionInternal(SectionIndex,
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Positions),
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Vertices),
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Triangles), true, UpdateFlags);
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
// Bail if we have no buffers to update.
|
|
if (UpdatedBuffers == ERuntimeMeshBuffer::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section and update bounding box
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
Section->LocalBoundingBox = BoundingBox;
|
|
|
|
// Finalize section update
|
|
UpdateSectionInternal(SectionIndex,
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Positions),
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Vertices),
|
|
!!(UpdatedBuffers & ERuntimeMeshBuffer::Triangles), true, UpdateFlags);
|
|
}
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
|
|
const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, bool bCreateCollision, EUpdateFrequency UpdateFrequency,
|
|
ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection);
|
|
|
|
// Validate all creation parameters
|
|
RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
|
|
|
|
// Create the section
|
|
auto NewSection = CreateOrResetSectionLegacyType(SectionIndex, 1);
|
|
|
|
// Update the mesh data in the section
|
|
NewSection->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, TArray<FVector2D>(), Colors);
|
|
|
|
TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
|
|
NewSection->UpdateIndexBuffer(TrianglesRef, false);
|
|
|
|
// Track collision status and update collision information if necessary
|
|
NewSection->CollisionEnabled = bCreateCollision;
|
|
NewSection->UpdateFrequency = UpdateFrequency;
|
|
|
|
// Finalize section.
|
|
CreateSectionInternal(SectionIndex, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
|
|
const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents,
|
|
bool bCreateCollision, EUpdateFrequency UpdateFrequency, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection_DualUV);
|
|
|
|
// Validate all creation parameters
|
|
RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
|
|
|
|
// Create the section
|
|
auto NewSection = CreateOrResetSectionLegacyType(SectionIndex, 2);
|
|
|
|
// Update the mesh data in the section
|
|
NewSection->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, UV1, Colors);
|
|
|
|
TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
|
|
NewSection->UpdateIndexBuffer(TrianglesRef, false);
|
|
|
|
// Track collision status and update collision information if necessary
|
|
NewSection->CollisionEnabled = bCreateCollision;
|
|
NewSection->UpdateFrequency = UpdateFrequency;
|
|
|
|
// Finalize section.
|
|
CreateSectionInternal(SectionIndex, UpdateFlags);
|
|
}
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0,
|
|
const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
UpdateMeshSection(SectionIndex, Vertices, TArray<int32>(), Normals, UV0, Colors, Tangents, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0,
|
|
const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
UpdateMeshSection(SectionIndex, Vertices, TArray<int32>(), Normals, UV0, UV1, Colors, Tangents, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
|
|
const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection);
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Validate section type
|
|
MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<FRuntimeMeshVertexSimple>();
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Tell the section to update the vertex buffer
|
|
bool bHadVertexUpdates = Section->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, TArray<FVector2D>(), Colors);
|
|
|
|
bool bHadTriangleUpdates = Triangles.Num() > 0;
|
|
if (bHadTriangleUpdates)
|
|
{
|
|
TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
|
|
|
|
Section->UpdateIndexBuffer(TrianglesRef, false);
|
|
}
|
|
|
|
UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
|
|
const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_DualUV);
|
|
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Validate section type
|
|
MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<FRuntimeMeshVertexDualUV>();
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Tell the section to update the vertex buffer
|
|
bool bHadVertexUpdates = Section->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, UV1, Colors);
|
|
|
|
bool bHadTriangleUpdates = Triangles.Num() > 0;
|
|
if (bHadTriangleUpdates)
|
|
{
|
|
TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
|
|
|
|
Section->UpdateIndexBuffer(TrianglesRef, false);
|
|
}
|
|
|
|
UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags);
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::CreateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents,
|
|
const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& VertexColors, bool bCreateCollision, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles, EUpdateFrequency UpdateFrequency)
|
|
{
|
|
// Convert vertex colors to FColor
|
|
TArray<FColor> Colors;
|
|
ConvertLinearColorToFColor(VertexColors, Colors);
|
|
|
|
ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None;
|
|
UpdateFlags |= bCalculateNormalTangent ? ESectionUpdateFlags::CalculateNormalTangent : ESectionUpdateFlags::None;
|
|
UpdateFlags |= bGenerateTessellationTriangles ? ESectionUpdateFlags::CalculateTessellationIndices : ESectionUpdateFlags::None;
|
|
|
|
// Create section
|
|
CreateMeshSection(SectionIndex, Vertices, Triangles, Normals, UV0, UV1, Colors, Tangents, bCreateCollision, UpdateFrequency, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents,
|
|
const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& VertexColors, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles)
|
|
{
|
|
// Convert vertex colors to FColor
|
|
TArray<FColor> Colors;
|
|
ConvertLinearColorToFColor(VertexColors, Colors);
|
|
|
|
ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None;
|
|
UpdateFlags |= bCalculateNormalTangent ? ESectionUpdateFlags::CalculateNormalTangent : ESectionUpdateFlags::None;
|
|
UpdateFlags |= bGenerateTessellationTriangles ? ESectionUpdateFlags::CalculateTessellationIndices : ESectionUpdateFlags::None;
|
|
|
|
// Update section
|
|
UpdateMeshSection(SectionIndex, Vertices, Triangles, Normals, UV0, UV1, Colors, Tangents, UpdateFlags);
|
|
}
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Triangles, bool bCreateCollision,
|
|
EUpdateFrequency UpdateFrequency, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
RMC_CHECKINGAME_LOGINEDITOR((SectionIndex >= 0), "SectionIndex cannot be negative.", /**/);
|
|
RMC_CHECKINGAME_LOGINEDITOR((Vertices.Length() > 0), "Vertices length must not be 0.", /**/);
|
|
RMC_CHECKINGAME_LOGINEDITOR((Triangles.Length() > 0), "Triangles length must not be 0", /**/);
|
|
|
|
// First we need to create the new section
|
|
TSharedPtr<FRuntimeMeshSectionInterface> Section = MakeShareable(Vertices.GetVertexType()->CreateSection(Vertices.WantsSeparatePositionBuffer()));
|
|
|
|
// Ensure sections array is long enough
|
|
if (SectionIndex >= MeshSections.Num())
|
|
{
|
|
MeshSections.SetNum(SectionIndex + 1, false);
|
|
}
|
|
|
|
// Set the new section
|
|
MeshSections[SectionIndex] = Section;
|
|
|
|
// Set vertex/index buffers
|
|
Section->UpdateVertexBuffer(Vertices, nullptr, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
|
|
Section->UpdateIndexBuffer(Triangles, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
|
|
|
|
|
|
// Track collision status and update collision information if necessary
|
|
Section->CollisionEnabled = bCreateCollision;
|
|
Section->UpdateFrequency = UpdateFrequency;
|
|
|
|
// Finalize section.
|
|
CreateSectionInternal(SectionIndex, UpdateFlags);
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Triangles, ESectionUpdateFlags UpdateFlags)
|
|
{
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Set vertex/index buffers
|
|
Section->UpdateVertexBuffer(Vertices, nullptr, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
|
|
Section->UpdateIndexBuffer(Triangles, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
|
|
|
|
UpdateSectionInternal(SectionIndex, Vertices.WantsSeparatePositionBuffer(), true, true, true, UpdateFlags);
|
|
}
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::ClearMeshSection(int32 SectionIndex)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearMeshSection);
|
|
|
|
if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
|
|
{
|
|
// Did this section have collision
|
|
bool HadCollision = MeshSections[SectionIndex]->CollisionEnabled;
|
|
bool bWasStaticSection = MeshSections[SectionIndex]->UpdateFrequency == EUpdateFrequency::Infrequent;
|
|
|
|
// Clear the section
|
|
MeshSections[SectionIndex].Reset();
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark section created
|
|
BatchState.MarkSectionDestroyed(SectionIndex, bWasStaticSection);
|
|
|
|
// Flag collision if this section affects it
|
|
if (HadCollision)
|
|
{
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
|
|
// Flag bounds update
|
|
BatchState.MarkBoundsDirty();
|
|
|
|
// bail since we don't update directly in this case.
|
|
return;
|
|
}
|
|
|
|
|
|
if (SceneProxy && !bWasStaticSection)
|
|
{
|
|
// Enqueue update on RT
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshSectionUpdate,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
int32, SectionIndex, SectionIndex,
|
|
{
|
|
RuntimeMeshSceneProxy->DestroySection_RenderThread(SectionIndex);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
MarkRenderStateDirty();
|
|
}
|
|
|
|
// Update our collision info only if this section had any influence on it
|
|
if (HadCollision)
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
|
|
UpdateLocalBounds();
|
|
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::ClearAllMeshSections()
|
|
{
|
|
MeshSections.Empty();
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkRenderStateDirty();
|
|
|
|
// Flag collision
|
|
BatchState.MarkCollisionDirty();
|
|
|
|
// Flag bounds update
|
|
BatchState.MarkBoundsDirty();
|
|
|
|
// bail since we don't update directly in this case.
|
|
return;
|
|
}
|
|
|
|
MarkRenderStateDirty();
|
|
MarkCollisionDirty();
|
|
UpdateLocalBounds();
|
|
}
|
|
|
|
void URuntimeMeshComponent::SetSectionTessellationTriangles(int32 SectionIndex, const TArray<int32>& TessellationTriangles, bool bShouldMoveArray)
|
|
{
|
|
// Validate all update parameters
|
|
RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get section
|
|
RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
|
|
|
|
// Tell the section to update the tessellation index buffer
|
|
Section->UpdateTessellationIndexBuffer(const_cast<TArray<int32>&>(TessellationTriangles), bShouldMoveArray);
|
|
|
|
UpdateSectionInternal(SectionIndex, false, false, true, false, ESectionUpdateFlags::None);
|
|
}
|
|
|
|
|
|
|
|
|
|
bool URuntimeMeshComponent::GetSectionBoundingBox(int32 SectionIndex, FBox& OutBoundingBox)
|
|
{
|
|
if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
|
|
{
|
|
OutBoundingBox = MeshSections[SectionIndex]->LocalBoundingBox;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void URuntimeMeshComponent::SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility)
|
|
{
|
|
if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
|
|
{
|
|
// Set game thread state
|
|
MeshSections[SectionIndex]->bIsVisible = bNewVisibility;
|
|
|
|
// Finish the update
|
|
UpdateSectionPropertiesInternal(SectionIndex, false);
|
|
}
|
|
}
|
|
|
|
bool URuntimeMeshComponent::IsMeshSectionVisible(int32 SectionIndex) const
|
|
{
|
|
return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->bIsVisible;
|
|
}
|
|
|
|
void URuntimeMeshComponent::SetMeshSectionCastsShadow(int32 SectionIndex, bool bNewCastsShadow)
|
|
{
|
|
if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
|
|
{
|
|
// Set game thread state
|
|
MeshSections[SectionIndex]->bCastsShadow = bNewCastsShadow;
|
|
|
|
// Finish the update
|
|
UpdateSectionPropertiesInternal(SectionIndex, true);
|
|
}
|
|
}
|
|
|
|
bool URuntimeMeshComponent::IsMeshSectionCastingShadows(int32 SectionIndex) const
|
|
{
|
|
return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->bCastsShadow;
|
|
}
|
|
|
|
void URuntimeMeshComponent::SetMeshSectionCollisionEnabled(int32 SectionIndex, bool bNewCollisionEnabled)
|
|
{
|
|
if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
|
|
{
|
|
auto& Section = MeshSections[SectionIndex];
|
|
if (Section->CollisionEnabled != bNewCollisionEnabled)
|
|
{
|
|
Section->CollisionEnabled = bNewCollisionEnabled;
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool URuntimeMeshComponent::IsMeshSectionCollisionEnabled(int32 SectionIndex)
|
|
{
|
|
return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->CollisionEnabled;
|
|
}
|
|
|
|
|
|
|
|
int32 URuntimeMeshComponent::GetNumSections() const
|
|
{
|
|
int32 SectionCount = 0;
|
|
for (int32 Index = 0; Index < MeshSections.Num(); Index++)
|
|
{
|
|
if (MeshSections[Index].IsValid())
|
|
{
|
|
SectionCount++;
|
|
}
|
|
}
|
|
|
|
return SectionCount;
|
|
}
|
|
|
|
bool URuntimeMeshComponent::DoesSectionExist(int32 SectionIndex) const
|
|
{
|
|
return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid();
|
|
}
|
|
|
|
int32 URuntimeMeshComponent::FirstAvailableMeshSectionIndex() const
|
|
{
|
|
for (int32 Index = 0; Index < MeshSections.Num(); Index++)
|
|
{
|
|
if (!MeshSections[Index].IsValid())
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
return MeshSections.Num();
|
|
}
|
|
|
|
int32 URuntimeMeshComponent::GetLastSectionIndex() const
|
|
{
|
|
for (int32 Index = MeshSections.Num() - 1; Index >= 0; Index--)
|
|
{
|
|
if (MeshSections[Index].IsValid())
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::SetMeshCollisionSection(int32 CollisionSectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_SetMeshCollisionSection);
|
|
|
|
if (MeshCollisionSections.Num() <= CollisionSectionIndex)
|
|
{
|
|
MeshCollisionSections.SetNum(CollisionSectionIndex + 1, false);
|
|
}
|
|
|
|
auto& Section = MeshCollisionSections[CollisionSectionIndex];
|
|
Section.VertexBuffer = Vertices;
|
|
Section.IndexBuffer = Triangles;
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::ClearMeshCollisionSection(int32 CollisionSectionIndex)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearMeshCollisionSection);
|
|
|
|
if (MeshCollisionSections.Num() <= CollisionSectionIndex)
|
|
return;
|
|
|
|
MeshCollisionSections[CollisionSectionIndex].Reset();
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::ClearAllMeshCollisionSections()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearAllMeshCollisionSections);
|
|
|
|
MeshCollisionSections.Empty();
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::AddCollisionConvexMesh(TArray<FVector> ConvexVerts)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_AddCollisionConvexMesh);
|
|
|
|
if (ConvexVerts.Num() >= 4)
|
|
{
|
|
FRuntimeConvexCollisionSection ConvexSection;
|
|
ConvexSection.VertexBuffer = ConvexVerts;
|
|
ConvexSection.BoundingBox = FBox(ConvexVerts);
|
|
ConvexCollisionSections.Add(ConvexSection);
|
|
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::ClearCollisionConvexMeshes()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearCollisionConvexMeshes);
|
|
|
|
// Empty simple collision info
|
|
ConvexCollisionSections.Empty();
|
|
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::SetCollisionConvexMeshes(const TArray< TArray<FVector> >& ConvexMeshes)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_SetCollisionConvexMeshes);
|
|
|
|
ConvexCollisionSections.Empty(ConvexMeshes.Num());
|
|
|
|
// Create element for each convex mesh
|
|
for (int32 ConvexIndex = 0; ConvexIndex < ConvexMeshes.Num(); ConvexIndex++)
|
|
{
|
|
FRuntimeConvexCollisionSection ConvexSection;
|
|
ConvexSection.VertexBuffer = ConvexMeshes[ConvexIndex];
|
|
ConvexSection.BoundingBox = FBox(ConvexSection.VertexBuffer);
|
|
ConvexCollisionSections.Add(ConvexSection);
|
|
}
|
|
|
|
|
|
// Use the batch update if one is running
|
|
if (BatchState.IsBatchPending())
|
|
{
|
|
// Mark render state dirty
|
|
BatchState.MarkCollisionDirty();
|
|
}
|
|
else
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::UpdateLocalBounds(bool bMarkRenderTransform)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateLocalBounds);
|
|
|
|
FBox LocalBox(EForceInit::ForceInitToZero);
|
|
|
|
for (const RuntimeMeshSectionPtr& Section : MeshSections)
|
|
{
|
|
if (Section.IsValid() && Section->bIsVisible)
|
|
{
|
|
LocalBox += Section->LocalBoundingBox;
|
|
}
|
|
}
|
|
|
|
LocalBounds = LocalBox.IsValid ? FBoxSphereBounds(LocalBox) : FBoxSphereBounds(FVector(0, 0, 0), FVector(0, 0, 0), 0); // fallback to reset box sphere bounds
|
|
|
|
// Update global bounds
|
|
UpdateBounds();
|
|
|
|
if (bMarkRenderTransform)
|
|
{
|
|
// Need to send to render thread
|
|
MarkRenderTransformDirty();
|
|
}
|
|
}
|
|
|
|
FPrimitiveSceneProxy* URuntimeMeshComponent::CreateSceneProxy()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateSceneProxy);
|
|
|
|
return new FRuntimeMeshSceneProxy(this);
|
|
}
|
|
|
|
int32 URuntimeMeshComponent::GetNumMaterials() const
|
|
{
|
|
return MeshSections.Num();
|
|
}
|
|
|
|
FBoxSphereBounds URuntimeMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
|
|
{
|
|
return LocalBounds.TransformBy(LocalToWorld);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::EndBatchUpdates()
|
|
{
|
|
// Bail if we have no pending updates
|
|
if (!BatchState.IsBatchPending())
|
|
return;
|
|
|
|
// Handle all pending rendering updates..
|
|
if (BatchState.RequiresSceneProxyRecreate())
|
|
{
|
|
MarkRenderStateDirty();
|
|
}
|
|
else
|
|
{
|
|
auto* BatchUpdateData = new FRuntimeMeshBatchUpdateData;
|
|
|
|
for (int32 Index = 0; Index <= BatchState.GetMaxSection(); Index++)
|
|
{
|
|
// Skip this section if it has no updates.
|
|
if (!BatchState.HasAnyFlagSet(Index))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check that we don't have both create and destroy flagged
|
|
check(!(BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Create) && BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Destroy)));
|
|
|
|
// Handle section created
|
|
if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Create))
|
|
{
|
|
// Validate section exists
|
|
check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
|
|
|
|
UMaterialInterface* Material = GetMaterial(Index);
|
|
if (Material == nullptr)
|
|
{
|
|
Material = UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
// Get the section create data and add it to the list
|
|
auto SectionCreateData = MeshSections[Index]->GetSectionCreationData(GetScene(), Material);
|
|
SectionCreateData->SetTargetSection(Index);
|
|
|
|
BatchUpdateData->CreateSections.Add(SectionCreateData);
|
|
}
|
|
// Handle destroy
|
|
else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Destroy))
|
|
{
|
|
BatchUpdateData->DestroySections.Add(Index);
|
|
}
|
|
// Handle vertex/index updates
|
|
else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::VerticesUpdate) || BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::IndicesUpdate))
|
|
{
|
|
// Validate section exists
|
|
check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
|
|
|
|
// Get the section update data and add it to the list.
|
|
bool bHadPositionUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::PositionsUpdate);
|
|
bool bHadVertexUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::VerticesUpdate);
|
|
bool bHadIndexUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::IndicesUpdate);
|
|
auto SectionUpdateData = MeshSections[Index]->GetSectionUpdateData(bHadPositionUpdates, bHadVertexUpdates, bHadIndexUpdates);
|
|
SectionUpdateData->SetTargetSection(Index);
|
|
|
|
BatchUpdateData->UpdateSections.Add(SectionUpdateData);
|
|
}
|
|
// Handle property updates
|
|
else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::PropertyUpdate))
|
|
{
|
|
// Validate section exists
|
|
check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
|
|
|
|
auto SectionProperties = new(BatchUpdateData->PropertyUpdateSections) FRuntimeMeshSectionPropertyUpdateData;
|
|
|
|
auto& Section = MeshSections[Index];
|
|
|
|
SectionProperties->SetTargetSection(Index);
|
|
SectionProperties->bIsVisible = Section->bIsVisible;
|
|
SectionProperties->bCastsShadow = Section->bCastsShadow;
|
|
}
|
|
else
|
|
{
|
|
// Unknown update type.
|
|
checkNoEntry();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Enqueue update on RT
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
FRuntimeMeshBatchUpdateCommand,
|
|
FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
|
|
FRuntimeMeshBatchUpdateData*, BatchUpdateData, BatchUpdateData,
|
|
{
|
|
RuntimeMeshSceneProxy->ApplyBatchUpdate_RenderThread(BatchUpdateData);
|
|
}
|
|
);
|
|
|
|
|
|
}
|
|
|
|
// Update collision if necessary
|
|
if (BatchState.RequiresCollisionUpdate())
|
|
{
|
|
MarkCollisionDirty();
|
|
}
|
|
|
|
// Update local bounds if necessary
|
|
if (BatchState.RequiresBoundsUpdate())
|
|
{
|
|
UpdateLocalBounds(!BatchState.RequiresSceneProxyRecreate());
|
|
}
|
|
|
|
// Clear batch info
|
|
BatchState.ResetBatch();
|
|
}
|
|
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::GetSectionMesh(int32 SectionIndex, const IRuntimeMeshVerticesBuilder*& Vertices, const FRuntimeMeshIndicesBuilder*& Indices)
|
|
{
|
|
RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
|
|
|
|
IRuntimeMeshVerticesBuilder* TempVertices;
|
|
FRuntimeMeshIndicesBuilder* TempIndices;
|
|
|
|
MeshSections[SectionIndex]->GetSectionMesh(TempVertices, TempIndices);
|
|
|
|
Vertices = TempVertices;
|
|
Indices = TempIndices;
|
|
}
|
|
|
|
void URuntimeMeshComponent::BeginMeshSectionUpdate(int32 SectionIndex, IRuntimeMeshVerticesBuilder*& Vertices, FRuntimeMeshIndicesBuilder*& Indices)
|
|
{
|
|
RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
|
|
|
|
// Get mesh
|
|
MeshSections[SectionIndex]->GetSectionMesh(Vertices, Indices);
|
|
}
|
|
|
|
bool URuntimeMeshComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_GetPhysicsTriMeshData);
|
|
int32 VertexBase = 0; // Base vertex index for current section
|
|
|
|
bool HadCollision = false;
|
|
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
|
|
// See if we should copy UVs
|
|
bool bCopyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults;
|
|
if (bCopyUVs)
|
|
{
|
|
CollisionData->UVs.AddZeroed(1); // only one UV channel
|
|
}
|
|
#endif
|
|
|
|
// For each section..
|
|
for (int32 SectionIdx = 0; SectionIdx < MeshSections.Num(); SectionIdx++)
|
|
{
|
|
const RuntimeMeshSectionPtr& Section = MeshSections[SectionIdx];
|
|
|
|
if (Section.IsValid() && Section->CollisionEnabled)
|
|
{
|
|
// Copy vertex data
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
|
|
Section->GetCollisionInformation(CollisionData->Vertices, CollisionData->UVs, bCopyUVs);
|
|
#else
|
|
Section->GetCollisionInformation(CollisionData->Vertices);
|
|
#endif
|
|
|
|
// Copy indices
|
|
const int32 NumTriangles = Section->IndexBuffer.Num() / 3;
|
|
for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++)
|
|
{
|
|
// Add the triangle
|
|
FTriIndices& Triangle = *new (CollisionData->Indices) FTriIndices;
|
|
Triangle.v0 = Section->IndexBuffer[(TriIdx * 3) + 0] + VertexBase;
|
|
Triangle.v1 = Section->IndexBuffer[(TriIdx * 3) + 1] + VertexBase;
|
|
Triangle.v2 = Section->IndexBuffer[(TriIdx * 3) + 2] + VertexBase;
|
|
|
|
// Add material info
|
|
CollisionData->MaterialIndices.Add(SectionIdx);
|
|
}
|
|
|
|
// Update the vertex base index
|
|
VertexBase = CollisionData->Vertices.Num();
|
|
HadCollision = true;
|
|
}
|
|
}
|
|
|
|
for (int32 SectionIdx = 0; SectionIdx < MeshCollisionSections.Num(); SectionIdx++)
|
|
{
|
|
auto& Section = MeshCollisionSections[SectionIdx];
|
|
if (Section.VertexBuffer.Num() > 0 && Section.IndexBuffer.Num() > 0)
|
|
{
|
|
CollisionData->Vertices.Append(Section.VertexBuffer);
|
|
|
|
const int32 NumTriangles = Section.IndexBuffer.Num() / 3;
|
|
for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++)
|
|
{
|
|
// Add the triangle
|
|
FTriIndices& Triangle = *new (CollisionData->Indices) FTriIndices;
|
|
Triangle.v0 = Section.IndexBuffer[(TriIdx * 3) + 0] + VertexBase;
|
|
Triangle.v1 = Section.IndexBuffer[(TriIdx * 3) + 1] + VertexBase;
|
|
Triangle.v2 = Section.IndexBuffer[(TriIdx * 3) + 2] + VertexBase;
|
|
|
|
// Add material info
|
|
CollisionData->MaterialIndices.Add(SectionIdx);
|
|
}
|
|
|
|
|
|
VertexBase = CollisionData->Vertices.Num();
|
|
HadCollision = true;
|
|
}
|
|
}
|
|
|
|
CollisionData->bFlipNormals = true;
|
|
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
|
|
if (CollisionMode == ERuntimeMeshCollisionCookingMode::CookingPerformance)
|
|
{
|
|
CollisionData->bFlipNormals = true;
|
|
}
|
|
#endif
|
|
|
|
return HadCollision;
|
|
}
|
|
|
|
bool URuntimeMeshComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const
|
|
{
|
|
for (const RuntimeMeshSectionPtr& Section : MeshSections)
|
|
{
|
|
if (Section.IsValid() && Section->IndexBuffer.Num() >= 3 && Section->CollisionEnabled)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const auto& Section : MeshCollisionSections)
|
|
{
|
|
if (Section.VertexBuffer.Num() > 0 && Section.IndexBuffer.Num() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::EnsureBodySetupCreated()
|
|
{
|
|
if (BodySetup == nullptr)
|
|
{
|
|
BodySetup = NewObject<UBodySetup>(this, NAME_None, (IsTemplate() ? RF_Public : RF_NoFlags));
|
|
BodySetup->BodySetupGuid = FGuid::NewGuid();
|
|
|
|
BodySetup->bGenerateMirroredCollision = false;
|
|
BodySetup->bDoubleSidedGeometry = true;
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::UpdateCollision()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateCollision);
|
|
|
|
bool NeedsNewPhysicsState = false;
|
|
|
|
// Destroy physics state if it exists
|
|
if (bPhysicsStateCreated)
|
|
{
|
|
DestroyPhysicsState();
|
|
NeedsNewPhysicsState = true;
|
|
}
|
|
|
|
// Ensure we have a BodySetup
|
|
EnsureBodySetupCreated();
|
|
|
|
// Fill in simple collision convex elements
|
|
BodySetup->AggGeom.ConvexElems.SetNum(ConvexCollisionSections.Num());
|
|
for (int32 Index = 0; Index < ConvexCollisionSections.Num(); Index++)
|
|
{
|
|
FKConvexElem& NewConvexElem = BodySetup->AggGeom.ConvexElems[Index];
|
|
|
|
NewConvexElem.VertexData = ConvexCollisionSections[Index].VertexBuffer;
|
|
NewConvexElem.ElemBox = FBox(NewConvexElem.VertexData);
|
|
}
|
|
|
|
// Set trace flag
|
|
BodySetup->CollisionTraceFlag = bUseComplexAsSimpleCollision ? CTF_UseComplexAsSimple : CTF_UseDefault;
|
|
|
|
// New GUID as collision has changed
|
|
BodySetup->BodySetupGuid = FGuid::NewGuid();
|
|
|
|
|
|
#if WITH_RUNTIME_PHYSICS_COOKING || WITH_PHYSX || WITH_EDITOR
|
|
// Clear current mesh data
|
|
BodySetup->InvalidatePhysicsData();
|
|
// Create new mesh data
|
|
BodySetup->CreatePhysicsMeshes();
|
|
#endif // WITH_RUNTIME_PHYSICS_COOKING || WITH_EDITOR
|
|
|
|
// Recreate physics state if necessary
|
|
if (NeedsNewPhysicsState)
|
|
{
|
|
CreatePhysicsState();
|
|
}
|
|
|
|
UpdateNavigation();
|
|
}
|
|
|
|
UBodySetup* URuntimeMeshComponent::GetBodySetup()
|
|
{
|
|
EnsureBodySetupCreated();
|
|
return BodySetup;
|
|
}
|
|
|
|
void URuntimeMeshComponent::MarkCollisionDirty()
|
|
{
|
|
if (!bCollisionDirty)
|
|
{
|
|
bCollisionDirty = true;
|
|
PrePhysicsTick.SetTickFunctionEnable(true);
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::CookCollisionNow()
|
|
{
|
|
if (bCollisionDirty)
|
|
{
|
|
BakeCollision();
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::BakeCollision()
|
|
{
|
|
// Bake the collision
|
|
UpdateCollision();
|
|
|
|
bCollisionDirty = false;
|
|
PrePhysicsTick.SetTickFunctionEnable(false);
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::UpdateNavigation()
|
|
{
|
|
if (UNavigationSystem::ShouldUpdateNavOctreeOnComponentChange() && IsRegistered())
|
|
{
|
|
UWorld* MyWorld = GetWorld();
|
|
|
|
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
|
|
if (MyWorld != nullptr && MyWorld->GetNavigationSystem() != nullptr &&
|
|
(MyWorld->GetNavigationSystem()->ShouldAllowClientSideNavigation() || !MyWorld->IsNetMode(ENetMode::NM_Client)))
|
|
#else
|
|
|
|
if (MyWorld != nullptr && MyWorld->IsGameWorld() && MyWorld->GetNetMode() < ENetMode::NM_Client)
|
|
#endif
|
|
{
|
|
UNavigationSystem::UpdateComponentInNavOctree(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::RegisterComponentTickFunctions(bool bRegister)
|
|
{
|
|
Super::RegisterComponentTickFunctions(bRegister);
|
|
|
|
if (bRegister)
|
|
{
|
|
if (SetupActorComponentTickFunction(&PrePhysicsTick))
|
|
{
|
|
PrePhysicsTick.Target = this;
|
|
PrePhysicsTick.SetTickFunctionEnable(bCollisionDirty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PrePhysicsTick.IsTickFunctionRegistered())
|
|
{
|
|
PrePhysicsTick.UnRegisterTickFunction();
|
|
}
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::SerializeLegacy(FArchive& Ar)
|
|
{
|
|
if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::Initial)
|
|
{
|
|
int32 SectionsCount = bShouldSerializeMeshData ? MeshSections.Num() : 0;
|
|
Ar << SectionsCount;
|
|
if (Ar.IsLoading() && MeshSections.Num() < SectionsCount)
|
|
{
|
|
MeshSections.SetNum(SectionsCount);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < SectionsCount; Index++)
|
|
{
|
|
bool IsSectionValid = MeshSections[Index].IsValid();
|
|
|
|
// WE can only load/save internal types (we don't know how to serialize arbitrary vertex types.
|
|
if (Ar.IsSaving() && (IsSectionValid && !MeshSections[Index]->bIsLegacySectionType))
|
|
{
|
|
IsSectionValid = false;
|
|
}
|
|
|
|
Ar << IsSectionValid;
|
|
|
|
if (IsSectionValid)
|
|
{
|
|
if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::TemplatedVertexFix)
|
|
{
|
|
int32 NumUVChannels;
|
|
bool WantsHalfPrecisionUVs;
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
MeshSections[Index]->GetInternalVertexComponents(NumUVChannels, WantsHalfPrecisionUVs);
|
|
}
|
|
|
|
Ar << NumUVChannels;
|
|
Ar << WantsHalfPrecisionUVs;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
CreateOrResetSectionLegacyType(Index, NumUVChannels);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
bool bWantsNormal;
|
|
bool bWantsTangent;
|
|
bool bWantsColor;
|
|
int32 TextureChannels;
|
|
|
|
Ar << bWantsNormal;
|
|
Ar << bWantsTangent;
|
|
Ar << bWantsColor;
|
|
Ar << TextureChannels;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
CreateOrResetSectionLegacyType(Index, TextureChannels);
|
|
}
|
|
}
|
|
|
|
FRuntimeMeshSectionInterface& SectionPtr = *MeshSections[Index].Get();
|
|
SectionPtr.Serialize(Ar);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::SerializationOptional)
|
|
{
|
|
|
|
if (bShouldSerializeMeshData || Ar.IsLoading())
|
|
{
|
|
// Serialize the real data if we want it, also use this path for loading to get anything that was in the last save
|
|
|
|
// Serialize the collision data
|
|
Ar << MeshCollisionSections;
|
|
Ar << ConvexCollisionSections;
|
|
}
|
|
else
|
|
{
|
|
// serialize empty arrays if we don't want serialization
|
|
TArray<FRuntimeMeshCollisionSection> NullCollisionSections;
|
|
Ar << NullCollisionSections;
|
|
TArray<FRuntimeConvexCollisionSection> NullConvexBodies;
|
|
Ar << NullConvexBodies;
|
|
}
|
|
}
|
|
}
|
|
|
|
void URuntimeMeshComponent::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
SerializeInternal(Ar);
|
|
}
|
|
|
|
void URuntimeMeshComponent::SerializeInternal(FArchive& Ar, bool bForceSaveAll)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_Serialize);
|
|
|
|
Ar.UsingCustomVersion(FRuntimeMeshVersion::GUID);
|
|
|
|
// Handle old serialization
|
|
if (Ar.CustomVer(FRuntimeMeshVersion::GUID) < FRuntimeMeshVersion::SerializationV2)
|
|
{
|
|
SerializeLegacy(Ar);
|
|
return;
|
|
}
|
|
|
|
bool bSerializeMeshData = bShouldSerializeMeshData || bForceSaveAll;
|
|
|
|
// Serialize basic settings
|
|
Ar << bSerializeMeshData;
|
|
Ar << bUseComplexAsSimpleCollision;
|
|
|
|
// Serialize the number of sections...
|
|
int32 NumSections = bSerializeMeshData ? MeshSections.Num() : 0;
|
|
Ar << NumSections;
|
|
|
|
// Resize the section array if we're loading.
|
|
if (Ar.IsLoading())
|
|
{
|
|
MeshSections.Reset(NumSections);
|
|
MeshSections.SetNum(NumSections);
|
|
}
|
|
|
|
// Next serialize all the sections...
|
|
for (int32 Index = 0; Index < NumSections; Index++)
|
|
{
|
|
SerializeRMCSection(Ar, Index);
|
|
}
|
|
|
|
if (bSerializeMeshData || Ar.IsLoading())
|
|
{
|
|
// Serialize the real data if we want it, also use this path for loading to get anything that was in the last save
|
|
|
|
// Serialize the collision data
|
|
Ar << MeshCollisionSections;
|
|
Ar << ConvexCollisionSections;
|
|
}
|
|
else
|
|
{
|
|
// serialize empty arrays if we don't want serialization
|
|
TArray<FRuntimeMeshCollisionSection> NullCollisionSections;
|
|
Ar << NullCollisionSections;
|
|
TArray<FRuntimeConvexCollisionSection> NullConvexBodies;
|
|
Ar << NullConvexBodies;
|
|
}
|
|
}
|
|
|
|
|
|
void URuntimeMeshComponent::SerializeRMC(FArchive& Ar)
|
|
{
|
|
SerializeInternal(Ar, true);
|
|
}
|
|
|
|
void URuntimeMeshComponent::SerializeRMCSection(FArchive& Ar, int32 SectionIndex)
|
|
{
|
|
if (Ar.IsLoading() && MeshSections.Num() <= SectionIndex)
|
|
{
|
|
MeshSections.SetNum(SectionIndex + 1);
|
|
}
|
|
|
|
// Serialize the section validity (default it to section valid + type known for saving reasons)
|
|
bool bSectionIsValid = MeshSections[SectionIndex].IsValid();
|
|
bool bSectionTypeFound = bSectionIsValid ? FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().GetVertexType(MeshSections[SectionIndex]->GetVertexType()->TypeGuid) != nullptr : true;
|
|
bSectionIsValid = bSectionIsValid && bSectionTypeFound;
|
|
Ar << bSectionIsValid;
|
|
|
|
// If section is invalid, skip
|
|
if (!bSectionIsValid)
|
|
{
|
|
if (!bSectionTypeFound)
|
|
{
|
|
UE_LOG(RuntimeMeshLog, Error, TEXT("Attempted to serialize a vertex of unknown type %s"), *MeshSections[SectionIndex]->GetVertexType()->TypeGuid.ToString());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Serialize section type info
|
|
FGuid TypeGuid;
|
|
bool bHasSeparatePositionBuffer;
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
TypeGuid = MeshSections[SectionIndex]->GetVertexType()->TypeGuid;
|
|
bHasSeparatePositionBuffer = MeshSections[SectionIndex]->IsDualBufferSection();
|
|
}
|
|
|
|
Ar << TypeGuid;
|
|
Ar << bHasSeparatePositionBuffer;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
auto VertexTypeRegistration = FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().GetVertexType(TypeGuid);
|
|
|
|
if (VertexTypeRegistration == nullptr)
|
|
{
|
|
UE_LOG(RuntimeMeshLog, Error, TEXT("Attempted to serialize a vertex of unknown type %s"), *MeshSections[SectionIndex]->GetVertexType()->TypeGuid.ToString());
|
|
bSectionIsValid = false;
|
|
}
|
|
else
|
|
{
|
|
auto NewSection = VertexTypeRegistration->CreateSection(bHasSeparatePositionBuffer);
|
|
MeshSections[SectionIndex] = MakeShareable(NewSection);
|
|
}
|
|
}
|
|
|
|
// Now we save the section data to a separate archive and then write in into the main.
|
|
// This way we can recover from unknown types or mismatch sizes
|
|
|
|
|
|
TArray<uint8> SectionData;
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
FMemoryWriter SectionAr(SectionData, true);
|
|
SectionAr.UsingCustomVersion(FRuntimeMeshVersion::GUID);
|
|
|
|
MeshSections[SectionIndex]->Serialize(SectionAr);
|
|
}
|
|
|
|
Ar << SectionData;
|
|
|
|
if (Ar.IsLoading() && bSectionIsValid)
|
|
{
|
|
FMemoryReader SectionAr(SectionData, true);
|
|
SectionAr.Seek(0);
|
|
SectionAr.UsingCustomVersion(FRuntimeMeshVersion::GUID);
|
|
|
|
MeshSections[SectionIndex]->Serialize(SectionAr);
|
|
|
|
// Was this section loaded correctly?
|
|
if (SectionAr.IsError())
|
|
{
|
|
MeshSections[SectionIndex].Reset();
|
|
UE_LOG(RuntimeMeshLog, Log, TEXT("Unable to load section %d of type %s. This is most likely caused by a reconfigured vertex type."),
|
|
SectionIndex, *MeshSections[SectionIndex]->GetVertexType()->TypeName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void URuntimeMeshComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
// Rebuild collision and local bounds.
|
|
MarkCollisionDirty();
|
|
UpdateLocalBounds();
|
|
|
|
if (BodySetup && IsTemplate())
|
|
{
|
|
BodySetup->SetFlags(RF_Public);
|
|
}
|
|
} |