// 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 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(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& 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 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& LinearColors, TArray& 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 URuntimeMeshComponent::CreateOrResetSectionLegacyType(int32 SectionIndex, int32 NumUVChannels) { if (NumUVChannels == 1) { return CreateOrResetSection>(SectionIndex, false, true); } else if (NumUVChannels == 2) { return CreateOrResetSection>(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& 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& 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* 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& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& UV0, const TArray& Colors, const TArray& 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(), Colors); TArray& TrianglesRef = const_cast&>(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& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& UV0, const TArray& UV1, const TArray& Colors, const TArray& 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& TrianglesRef = const_cast&>(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& Vertices, const TArray& Normals, const TArray& UV0, const TArray& Colors, const TArray& Tangents, ESectionUpdateFlags UpdateFlags) { UpdateMeshSection(SectionIndex, Vertices, TArray(), Normals, UV0, Colors, Tangents, UpdateFlags); } void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray& Vertices, const TArray& Normals, const TArray& UV0, const TArray& UV1, const TArray& Colors, const TArray& Tangents, ESectionUpdateFlags UpdateFlags) { UpdateMeshSection(SectionIndex, Vertices, TArray(), Normals, UV0, UV1, Colors, Tangents, UpdateFlags); } void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& UV0, const TArray& Colors, const TArray& 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(); // Get section RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex]; // Tell the section to update the vertex buffer bool bHadVertexUpdates = Section->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, TArray(), Colors); bool bHadTriangleUpdates = Triangles.Num() > 0; if (bHadTriangleUpdates) { TArray& TrianglesRef = const_cast&>(Triangles); Section->UpdateIndexBuffer(TrianglesRef, false); } UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags); } void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& UV0, const TArray& UV1, const TArray& Colors, const TArray& 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(); // 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& TrianglesRef = const_cast&>(Triangles); Section->UpdateIndexBuffer(TrianglesRef, false); } UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags); } void URuntimeMeshComponent::CreateMeshSection_Blueprint(int32 SectionIndex, const TArray& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& Tangents, const TArray& UV0, const TArray& UV1, const TArray& VertexColors, bool bCreateCollision, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles, EUpdateFrequency UpdateFrequency) { // Convert vertex colors to FColor TArray 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& Vertices, const TArray& Triangles, const TArray& Normals, const TArray& Tangents, const TArray& UV0, const TArray& UV1, const TArray& VertexColors, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles) { // Convert vertex colors to FColor TArray 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 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& 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&>(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& Vertices, const TArray& 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 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 >& 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(0); 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(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_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 NullCollisionSections; Ar << NullCollisionSections; TArray 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 NullCollisionSections; Ar << NullCollisionSections; TArray 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 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); } }