// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshPaintStaticMeshAdapter.h" #include "StaticMeshComponentLODInfo.h" #include "StaticMeshResources.h" #include "PhysicsEngine/BodySetup.h" #include "MeshPaintHelpers.h" #include "ComponentReregisterContext.h" #include "MeshPaintTypes.h" #include "Engine/StaticMesh.h" ////////////////////////////////////////////////////////////////////////// // FMeshPaintGeometryAdapterForStaticMeshes FMeshPaintGeometryAdapterForStaticMeshes::FMeshToComponentMap FMeshPaintGeometryAdapterForStaticMeshes::MeshToComponentMap; bool FMeshPaintGeometryAdapterForStaticMeshes::Construct(UMeshComponent* InComponent, int32 InMeshLODIndex) { StaticMeshComponent = Cast(InComponent); if (StaticMeshComponent != nullptr) { StaticMeshComponent->OnStaticMeshChanged().AddRaw(this, &FMeshPaintGeometryAdapterForStaticMeshes::OnStaticMeshChanged); if (StaticMeshComponent->GetStaticMesh() != nullptr) { ReferencedStaticMesh = StaticMeshComponent->GetStaticMesh(); MeshLODIndex = InMeshLODIndex; ReferencedStaticMesh->OnPostMeshBuild().AddRaw(this, &FMeshPaintGeometryAdapterForStaticMeshes::OnPostMeshBuild); const bool bSuccess = Initialize(); return bSuccess; } } return false; } FMeshPaintGeometryAdapterForStaticMeshes::~FMeshPaintGeometryAdapterForStaticMeshes() { if (StaticMeshComponent != nullptr) { if (ReferencedStaticMesh != nullptr) { ReferencedStaticMesh->OnPostMeshBuild().RemoveAll(this); } StaticMeshComponent->OnStaticMeshChanged().RemoveAll(this); } } void FMeshPaintGeometryAdapterForStaticMeshes::OnPostMeshBuild(UStaticMesh* StaticMesh) { check(StaticMesh == ReferencedStaticMesh); Initialize(); } void FMeshPaintGeometryAdapterForStaticMeshes::OnStaticMeshChanged(UStaticMeshComponent* InStaticMeshComponent) { check(StaticMeshComponent == InStaticMeshComponent); OnRemoved(); ReferencedStaticMesh->OnPostMeshBuild().RemoveAll(this); ReferencedStaticMesh = InStaticMeshComponent->GetStaticMesh(); if (ReferencedStaticMesh) { ReferencedStaticMesh->OnPostMeshBuild().AddRaw(this, &FMeshPaintGeometryAdapterForStaticMeshes::OnPostMeshBuild); Initialize(); OnAdded(); } } bool FMeshPaintGeometryAdapterForStaticMeshes::Initialize() { check(ReferencedStaticMesh == StaticMeshComponent->GetStaticMesh()); if (MeshLODIndex < ReferencedStaticMesh->GetNumLODs()) { LODModel = &(ReferencedStaticMesh->GetRenderData()->LODResources[MeshLODIndex]); return FBaseMeshPaintGeometryAdapter::Initialize(); } return false; } bool FMeshPaintGeometryAdapterForStaticMeshes::InitializeVertexData() { // Retrieve mesh vertex and index data const int32 NumVertices = LODModel->VertexBuffers.PositionVertexBuffer.GetNumVertices(); MeshVertices.Reset(); MeshVertices.AddDefaulted(NumVertices); for (int32 Index = 0; Index < NumVertices; Index++) { const FVector& Position = (FVector)LODModel->VertexBuffers.PositionVertexBuffer.VertexPosition(Index); MeshVertices[Index] = Position; } const int32 NumIndices = LODModel->IndexBuffer.GetNumIndices(); MeshIndices.Reset(); MeshIndices.AddDefaulted(NumIndices); const FIndexArrayView ArrayView = LODModel->IndexBuffer.GetArrayView(); for (int32 Index = 0; Index < NumIndices; Index++) { MeshIndices[Index] = ArrayView[Index]; } return (MeshVertices.Num() > 0 && MeshIndices.Num() > 0); } void FMeshPaintGeometryAdapterForStaticMeshes::PostEdit() { // Lighting does not need to be invalidated when mesh painting const bool bUnbuildLighting = false; // Recreate all component states using the referenced static mesh FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(ReferencedStaticMesh, bUnbuildLighting); const bool bUsingInstancedVertexColors = true; // Currently we are only painting to instances // Update gpu resource data if (bUsingInstancedVertexColors) { // We're only changing instanced vertices on this specific mesh component, so we // only need to detach our mesh component FComponentReregisterContext ComponentReregisterContext(StaticMeshComponent); // If LOD is 0, post-edit all LODs. There's currently no way to tell from here // if VertexPaintSettings.bPaintOnSpecificLOD is set to true or not. const int32 MaxLOD = (MeshLODIndex == 0) ? StaticMeshComponent->LODData.Num() : (MeshLODIndex + 1); for (int32 Index = MeshLODIndex; Index < MaxLOD; ++Index) { BeginInitResource(StaticMeshComponent->LODData[Index].OverrideVertexColors); } } else { // Reinitialize the static mesh's resources. ReferencedStaticMesh->InitResources(); } } void FMeshPaintGeometryAdapterForStaticMeshes::InitializeAdapterGlobals() { static bool bInitialized = false; if (!bInitialized) { bInitialized = true; MeshToComponentMap.Empty(); } } void FMeshPaintGeometryAdapterForStaticMeshes::CleanupGlobals() { for (auto& Pair : MeshToComponentMap) { if (Pair.Key && Pair.Value.RestoreBodySetup) { Pair.Key->SetBodySetup(Pair.Value.RestoreBodySetup); } } MeshToComponentMap.Empty(); } void FMeshPaintGeometryAdapterForStaticMeshes::OnAdded() { check(StaticMeshComponent); check(ReferencedStaticMesh); check(ReferencedStaticMesh == StaticMeshComponent->GetStaticMesh()); FStaticMeshReferencers& StaticMeshReferencers = MeshToComponentMap.FindOrAdd(ReferencedStaticMesh); check(!StaticMeshReferencers.Referencers.ContainsByPredicate( [this](const FStaticMeshReferencers::FReferencersInfo& Info) { return Info.StaticMeshComponent == this->StaticMeshComponent; } )); bool bBodyChanged = false; // If this is the first attempt to add a temporary body setup to the mesh, do it if (StaticMeshReferencers.Referencers.Num() == 0) { // Remember the old body setup (this will be added as a GC reference so that it doesn't get destroyed) StaticMeshReferencers.RestoreBodySetup = ReferencedStaticMesh->GetBodySetup(); // Create a new body setup from the mesh's main body setup. This has to have the static mesh as its outer, // otherwise the body instance will not be created correctly. UBodySetup* TempBodySetupRaw = DuplicateObject(ReferencedStaticMesh->GetBodySetup(), ReferencedStaticMesh); TempBodySetupRaw->ClearFlags(RF_Transactional); // Set collide all flag so that the body creates physics meshes using ALL elements from the mesh not just the collision mesh. TempBodySetupRaw->bMeshCollideAll = true; // This forces it to recreate the physics mesh. TempBodySetupRaw->InvalidatePhysicsData(); // Force it to use high detail tri-mesh for collisions. TempBodySetupRaw->CollisionTraceFlag = CTF_UseComplexAsSimple; TempBodySetupRaw->AggGeom.ConvexElems.Empty(); // Set as new body setup ReferencedStaticMesh->SetBodySetup(TempBodySetupRaw); bBodyChanged = true; } ECollisionEnabled::Type CachedCollisionType = StaticMeshComponent->BodyInstance.GetCollisionEnabled(); StaticMeshReferencers.Referencers.Emplace(StaticMeshComponent, CachedCollisionType); // Force the collision type to not be 'NoCollision' without it the line trace will always fail. if (CachedCollisionType == ECollisionEnabled::NoCollision) { StaticMeshComponent->BodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryOnly, false); } if (bBodyChanged) { // Set new physics state for the component StaticMeshComponent->RecreatePhysicsState(); } } void FMeshPaintGeometryAdapterForStaticMeshes::OnRemoved() { // If the referenced static mesh has been destroyed (and nulled by GC), don't try to do anything more. // It should be in the process of removing all global geometry adapters if it gets here in this situation. if (!ReferencedStaticMesh || !StaticMeshComponent) { return; } // Remove a reference from the static mesh map FStaticMeshReferencers* StaticMeshReferencers = MeshToComponentMap.Find(ReferencedStaticMesh); if (StaticMeshReferencers) { check(StaticMeshReferencers->Referencers.Num() > 0); int32 Index = StaticMeshReferencers->Referencers.IndexOfByPredicate( [this](const FStaticMeshReferencers::FReferencersInfo& Info) { return Info.StaticMeshComponent == this->StaticMeshComponent; } ); if(Index != INDEX_NONE) { StaticMeshComponent->BodyInstance.SetCollisionEnabled(StaticMeshReferencers->Referencers[Index].CachedCollisionType, false); StaticMeshComponent->RecreatePhysicsState(); StaticMeshReferencers->Referencers.RemoveAtSwap(Index); } else { // Might be null components. Remove them StaticMeshReferencers->Referencers.RemoveAll([=](const FStaticMeshReferencers::FReferencersInfo& Info) { return Info.StaticMeshComponent == nullptr; }); } // If the last reference was removed, restore the body setup for the static mesh if (StaticMeshReferencers->Referencers.Num() == 0) { ReferencedStaticMesh->SetBodySetup(StaticMeshReferencers->RestoreBodySetup); verify(MeshToComponentMap.Remove(ReferencedStaticMesh) == 1); } } } bool FMeshPaintGeometryAdapterForStaticMeshes::LineTraceComponent(struct FHitResult& OutHit, const FVector Start, const FVector End, const struct FCollisionQueryParams& Params) const { // Ray trace return StaticMeshComponent->LineTraceComponent(OutHit, Start, End, Params); } void FMeshPaintGeometryAdapterForStaticMeshes::QueryPaintableTextures(int32 MaterialIndex, int32& OutDefaultIndex, TArray& InOutTextureList) { DefaultQueryPaintableTextures(MaterialIndex, StaticMeshComponent, OutDefaultIndex, InOutTextureList); } void FMeshPaintGeometryAdapterForStaticMeshes::ApplyOrRemoveTextureOverride(UTexture* SourceTexture, UTexture* OverrideTexture) const { DefaultApplyOrRemoveTextureOverride(StaticMeshComponent, SourceTexture, OverrideTexture); } void FMeshPaintGeometryAdapterForStaticMeshes::AddReferencedObjectsGlobals(FReferenceCollector& Collector) { for (auto& Pair : MeshToComponentMap) { Collector.AddReferencedObject(Pair.Key); Collector.AddReferencedObject(Pair.Value.RestoreBodySetup); for (FStaticMeshReferencers::FReferencersInfo& ReferencerInfo : Pair.Value.Referencers) { Collector.AddReferencedObject(ReferencerInfo.StaticMeshComponent); } } } void FMeshPaintGeometryAdapterForStaticMeshes::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(ReferencedStaticMesh); Collector.AddReferencedObject(StaticMeshComponent); } void FMeshPaintGeometryAdapterForStaticMeshes::GetVertexColor(int32 VertexIndex, FColor& OutColor, bool bInstance /*= true*/) const { if (bInstance) { FStaticMeshComponentLODInfo* InstanceMeshLODInfo = &StaticMeshComponent->LODData[MeshLODIndex]; if (!bInstance && LODModel->VertexBuffers.ColorVertexBuffer.GetNumVertices() == 0) { // Mesh doesn't have a color vertex buffer yet! We'll create one now. LODModel->VertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor(255, 255, 255, 255), LODModel->GetNumVertices()); // @todo MeshPaint: Make sure this is the best place to do this BeginInitResource(&LODModel->VertexBuffers.ColorVertexBuffer); } // Actor mesh component LOD const bool bValidInstanceData = InstanceMeshLODInfo && InstanceMeshLODInfo->OverrideVertexColors && InstanceMeshLODInfo->OverrideVertexColors->GetNumVertices() == LODModel->GetNumVertices(); if (bValidInstanceData) { OutColor = InstanceMeshLODInfo->OverrideVertexColors->VertexColor(VertexIndex); } } else { // Static mesh LOD const bool bValidMeshData = LODModel->VertexBuffers.ColorVertexBuffer.GetNumVertices() > (uint32)VertexIndex; if (bValidMeshData) { OutColor = LODModel->VertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex); } } } void FMeshPaintGeometryAdapterForStaticMeshes::SetVertexColor(int32 VertexIndex, FColor Color, bool bInstance /*= true*/) { // Update the mesh! if (bInstance) { FStaticMeshComponentLODInfo* InstanceMeshLODInfo = &StaticMeshComponent->LODData[MeshLODIndex]; const bool bValidInstanceData = InstanceMeshLODInfo && InstanceMeshLODInfo->OverrideVertexColors && InstanceMeshLODInfo->OverrideVertexColors->GetNumVertices() == LODModel->GetNumVertices(); // If there is valid instance data update the color value if (bValidInstanceData) { check(InstanceMeshLODInfo->OverrideVertexColors); check((uint32)VertexIndex < InstanceMeshLODInfo->OverrideVertexColors->GetNumVertices()); check(InstanceMeshLODInfo->OverrideVertexColors->GetNumVertices() == InstanceMeshLODInfo->PaintedVertices.Num()); InstanceMeshLODInfo->OverrideVertexColors->VertexColor(VertexIndex) = Color; InstanceMeshLODInfo->PaintedVertices[VertexIndex].Color = Color; // If set on LOD level > 0 means we have per LOD painted vertex color data if (MeshLODIndex > 0) { StaticMeshComponent->bCustomOverrideVertexColorPerLOD = true; } } } else { const bool bValidMeshData = LODModel->VertexBuffers.ColorVertexBuffer.GetNumVertices() >(uint32)VertexIndex; if (bValidMeshData) { LODModel->VertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex) = Color; } } } FMatrix FMeshPaintGeometryAdapterForStaticMeshes::GetComponentToWorldMatrix() const { return StaticMeshComponent->GetComponentToWorld().ToMatrixWithScale(); } void FMeshPaintGeometryAdapterForStaticMeshes::GetTextureCoordinate(int32 VertexIndex, int32 ChannelIndex, FVector2D& OutTextureCoordinate) const { OutTextureCoordinate = FVector2D(LODModel->VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, ChannelIndex)); } void FMeshPaintGeometryAdapterForStaticMeshes::PreEdit() { const bool bUsingInstancedVertexColors = true; // Currently we are only painting to instances UStaticMesh* StaticMesh = ReferencedStaticMesh; if (bUsingInstancedVertexColors) { // Mark the mesh component as modified StaticMeshComponent->SetFlags(RF_Transactional); StaticMeshComponent->Modify(); StaticMeshComponent->bCustomOverrideVertexColorPerLOD = (MeshLODIndex > 0); const int32 NumLODs = StaticMesh->GetNumLODs(); const int32 MaxIndex = (MeshLODIndex == 0) ? NumLODs : (MeshLODIndex + 1); // Ensure LODData has enough entries in it, free not required. StaticMeshComponent->SetLODDataCount(NumLODs, NumLODs); // If LOD is 0, pre-edit all LODs. There's currently no way to tell from here // if VertexPaintSettings.bPaintOnSpecificLOD is set to true or not. for (int32 Index = MeshLODIndex; Index < MaxIndex; ++Index) { FStaticMeshComponentLODInfo& InstanceMeshLODInfo = StaticMeshComponent->LODData[Index]; FStaticMeshLODResources& LODResource = StaticMesh->GetRenderData()->LODResources[Index]; // Destroy the instance vertex color array if it doesn't fit if (InstanceMeshLODInfo.OverrideVertexColors && InstanceMeshLODInfo.OverrideVertexColors->GetNumVertices() != LODResource.GetNumVertices()) { InstanceMeshLODInfo.ReleaseOverrideVertexColorsAndBlock(); } if (InstanceMeshLODInfo.OverrideVertexColors) { // Destroy the cached paint data every paint. Painting redefines the source data. InstanceMeshLODInfo.PaintedVertices.Empty(); InstanceMeshLODInfo.BeginReleaseOverrideVertexColors(); FlushRenderingCommands(); } else { // Setup the instance vertex color array if we don't have one yet InstanceMeshLODInfo.OverrideVertexColors = new FColorVertexBuffer; if ((int32)LODResource.VertexBuffers.ColorVertexBuffer.GetNumVertices() >= LODResource.GetNumVertices()) { // copy mesh vertex colors to the instance ones InstanceMeshLODInfo.OverrideVertexColors->InitFromColorArray(&LODResource.VertexBuffers.ColorVertexBuffer.VertexColor(0), LODResource.GetNumVertices()); } else { // Original mesh didn't have any colors, so just use a default color InstanceMeshLODInfo.OverrideVertexColors->InitFromSingleColor(FColor::White, LODResource.GetNumVertices()); } } } // See if the component has to cache its mesh vertex positions associated with override colors StaticMeshComponent->CachePaintedDataIfNecessary(); StaticMeshComponent->StaticMeshDerivedDataKey = StaticMesh->GetRenderData()->DerivedDataKey; } else { // Dirty the mesh StaticMesh->SetFlags(RF_Transactional); StaticMesh->Modify(); // Release the static mesh's resources. StaticMesh->ReleaseResources(); // Flush the resource release commands to the rendering thread to ensure that the build doesn't occur while a resource is still // allocated, and potentially accessing the UStaticMesh. StaticMesh->ReleaseResourcesFence.Wait(); } } ////////////////////////////////////////////////////////////////////////// // FMeshPaintGeometryAdapterForStaticMeshesFactory TSharedPtr FMeshPaintGeometryAdapterForStaticMeshesFactory::Construct(class UMeshComponent* InComponent, int32 InMeshLODIndex) const { if (UStaticMeshComponent* StaticMeshComponent = Cast(InComponent)) { if (StaticMeshComponent->GetStaticMesh() != nullptr) { TSharedRef Result = MakeShareable(new FMeshPaintGeometryAdapterForStaticMeshes()); if (Result->Construct(InComponent, InMeshLODIndex)) { return Result; } } } return nullptr; }