Files
UnrealEngine/Engine/Plugins/MeshPainting/Source/MeshPaintingToolset/Private/MeshPaintStaticMeshAdapter.cpp
2025-05-18 13:04:45 +08:00

465 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshPaintStaticMeshAdapter.h"
#include "StaticMeshComponentLODInfo.h"
#include "StaticMeshResources.h"
#include "ComponentReregisterContext.h"
#include "Engine/StaticMesh.h"
//////////////////////////////////////////////////////////////////////////
// FMeshPaintGeometryAdapterForStaticMeshes
bool FMeshPaintStaticMeshComponentAdapter::Construct(UMeshComponent* InComponent, int32 InMeshLODIndex)
{
StaticMeshComponent = Cast<UStaticMeshComponent>(InComponent);
if (StaticMeshComponent.IsValid())
{
#if WITH_EDITOR
StaticMeshComponent->OnStaticMeshChanged().AddRaw(this, &FMeshPaintStaticMeshComponentAdapter::OnStaticMeshChanged);
#endif
if (StaticMeshComponent->GetStaticMesh() != nullptr)
{
ReferencedStaticMesh = StaticMeshComponent->GetStaticMesh();
MeshLODIndex = InMeshLODIndex;
#if WITH_EDITOR
ReferencedStaticMesh->OnPostMeshBuild().AddRaw(this, &FMeshPaintStaticMeshComponentAdapter::OnPostMeshBuild);
#endif
const bool bSuccess = Initialize();
return bSuccess;
}
}
return false;
}
FMeshPaintStaticMeshComponentAdapter::~FMeshPaintStaticMeshComponentAdapter()
{
#if WITH_EDITOR
if (StaticMeshComponent.IsValid())
{
StaticMeshComponent->OnStaticMeshChanged().RemoveAll(this);
}
if (ReferencedStaticMesh != nullptr)
{
ReferencedStaticMesh->OnPostMeshBuild().RemoveAll(this);
}
#endif
}
void FMeshPaintStaticMeshComponentAdapter::OnPostMeshBuild(UStaticMesh* StaticMesh)
{
check(StaticMesh == ReferencedStaticMesh);
Initialize();
}
void FMeshPaintStaticMeshComponentAdapter::OnStaticMeshChanged(UStaticMeshComponent* InStaticMeshComponent)
{
check(StaticMeshComponent == InStaticMeshComponent);
OnRemoved();
if (ReferencedStaticMesh)
{
#if WITH_EDITOR
ReferencedStaticMesh->OnPostMeshBuild().RemoveAll(this);
#endif
ReferencedStaticMesh = InStaticMeshComponent->GetStaticMesh();
#if WITH_EDITOR
ReferencedStaticMesh->OnPostMeshBuild().AddRaw(this, &FMeshPaintStaticMeshComponentAdapter::OnPostMeshBuild);
#endif
Initialize();
OnAdded();
}
}
bool FMeshPaintStaticMeshComponentAdapter::Initialize()
{
if (StaticMeshComponent.IsValid())
{
check(ReferencedStaticMesh == StaticMeshComponent->GetStaticMesh());
if (MeshLODIndex < ReferencedStaticMesh->GetNumLODs())
{
LODModel = &(ReferencedStaticMesh->GetRenderData()->LODResources[MeshLODIndex]);
return FBaseMeshPaintComponentAdapter::Initialize();
}
}
return false;
}
bool FMeshPaintStaticMeshComponentAdapter::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 FMeshPaintStaticMeshComponentAdapter::PostEdit()
{
// We shouldn't assume that the cached static mesh component remains valid.
// Components may be destroyed by editor ticks, and be forcibly removed by GC.
if (!StaticMeshComponent.IsValid())
{
return;
}
// 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.Get());
// 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 FMeshPaintStaticMeshComponentAdapter::InitializeAdapterGlobals()
{
static bool bInitialized = false;
if (!bInitialized)
{
bInitialized = true;
}
}
void FMeshPaintStaticMeshComponentAdapter::AddReferencedObjectsGlobals(FReferenceCollector& Collector)
{
}
void FMeshPaintStaticMeshComponentAdapter::CleanupGlobals()
{
}
void FMeshPaintStaticMeshComponentAdapter::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(ReferencedStaticMesh);
}
int32 FMeshPaintStaticMeshComponentAdapter::GetNumUVChannels() const
{
return LODModel == nullptr ? 0 : LODModel->GetNumTexCoords();
}
bool FMeshPaintStaticMeshComponentAdapter::LineTraceComponent(struct FHitResult& OutHit, const FVector Start, const FVector End, const struct FCollisionQueryParams& Params) const
{
if (!StaticMeshComponent.IsValid())
{
return false;
}
// Ray trace
const bool bHitBounds = FMath::LineSphereIntersection(Start, End.GetSafeNormal(), (End - Start).SizeSquared(), StaticMeshComponent->Bounds.Origin, StaticMeshComponent->Bounds.SphereRadius);
const float SqrRadius = FMath::Square(StaticMeshComponent->Bounds.SphereRadius);
const bool bInsideBounds = (StaticMeshComponent->Bounds.ComputeSquaredDistanceFromBoxToPoint(Start) <= SqrRadius) || (StaticMeshComponent->Bounds.ComputeSquaredDistanceFromBoxToPoint(End) <= SqrRadius);
bool bHitTriangle = false;
if (bHitBounds || bInsideBounds)
{
const FTransform& ComponentTransform = StaticMeshComponent->GetComponentTransform();
const FVector LocalStart = ComponentTransform.InverseTransformPosition(Start);
const FVector LocalEnd = ComponentTransform.InverseTransformPosition(End);
float MinDistance = FLT_MAX;
FVector Intersect;
FVector Normal;
UE::Geometry::FIndex3i FoundTriangle;
FVector HitPosition;
if (!RayIntersectAdapter(FoundTriangle, HitPosition, LocalStart, LocalEnd))
{
return false;
}
// Compute the normal of the triangle
const FVector& P0 = MeshVertices[FoundTriangle.A];
const FVector& P1 = MeshVertices[FoundTriangle.B];
const FVector& P2 = MeshVertices[FoundTriangle.C];
UE::Geometry::FTriangle3d Triangle((FVector3d)P0, (FVector3d)P1, (FVector3d)P2);
FVector3d TriNormal = Triangle.Normal();
//check collinearity of A,B,C
if (TriNormal.SquaredLength() > (double)SMALL_NUMBER)
{
FVector3d RayDirection = ((FVector3d)LocalEnd - (FVector3d)LocalStart);
FRay3d LocalRay((FVector3d)LocalStart, UE::Geometry::Normalized(RayDirection));
UE::Geometry::FIntrRay3Triangle3d RayTriIntersection(LocalRay, Triangle);
if (RayTriIntersection.Find())
{
double Distance = RayTriIntersection.RayParameter;
if (Distance < MinDistance)
{
MinDistance = (float)Distance;
Intersect = (FVector)LocalRay.PointAt(RayTriIntersection.RayParameter);
Normal = (FVector)TriNormal;
}
}
}
if (MinDistance != FLT_MAX)
{
OutHit.Component = StaticMeshComponent;
OutHit.Normal = ComponentTransform.TransformVector(Normal).GetSafeNormal();
OutHit.ImpactNormal = OutHit.Normal;
OutHit.ImpactPoint = ComponentTransform.TransformPosition(Intersect);
OutHit.Location = OutHit.ImpactPoint;
OutHit.bBlockingHit = true;
OutHit.Distance = MinDistance;
bHitTriangle = true;
}
}
return bHitTriangle;
}
void FMeshPaintStaticMeshComponentAdapter::QueryPaintableTextures(int32 MaterialIndex, int32& OutDefaultIndex, TArray<struct FPaintableTexture>& InOutTextureList)
{
if (StaticMeshComponent.IsValid())
{
DefaultQueryPaintableTextures(MaterialIndex, StaticMeshComponent.Get(), OutDefaultIndex, InOutTextureList);
}
}
void FMeshPaintStaticMeshComponentAdapter::ApplyOrRemoveTextureOverride(UTexture* SourceTexture, UTexture* OverrideTexture) const
{
if (StaticMeshComponent.IsValid())
{
TextureOverridesState.ApplyOrRemoveTextureOverride(StaticMeshComponent.Get(), SourceTexture, OverrideTexture);
}
}
void FMeshPaintStaticMeshComponentAdapter::GetVertexColor(int32 VertexIndex, FColor& OutColor, bool bInstance /*= true*/) const
{
if (bInstance)
{
if (!StaticMeshComponent.IsValid())
{
OutColor = FColor::White;
return;
}
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 FMeshPaintStaticMeshComponentAdapter::SetVertexColor(int32 VertexIndex, FColor Color, bool bInstance /*= true*/)
{
// Update the mesh!
if (bInstance)
{
if (!StaticMeshComponent.IsValid())
{
return;
}
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 WITH_EDITOR
// If set on LOD level > 0 means we have per LOD painted vertex color data
if (MeshLODIndex > 0)
{
StaticMeshComponent->bCustomOverrideVertexColorPerLOD = true;
}
#endif
}
}
else
{
const bool bValidMeshData = LODModel->VertexBuffers.ColorVertexBuffer.GetNumVertices() >(uint32)VertexIndex;
if (bValidMeshData)
{
LODModel->VertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex) = Color;
}
}
}
FMatrix FMeshPaintStaticMeshComponentAdapter::GetComponentToWorldMatrix() const
{
if (!StaticMeshComponent.IsValid())
{
return FMatrix::Identity;
}
return StaticMeshComponent->GetComponentToWorld().ToMatrixWithScale();
}
void FMeshPaintStaticMeshComponentAdapter::GetTextureCoordinate(int32 VertexIndex, int32 ChannelIndex, FVector2D& OutTextureCoordinate) const
{
OutTextureCoordinate = FVector2D(LODModel->VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, ChannelIndex));
}
void FMeshPaintStaticMeshComponentAdapter::PreEdit()
{
if (!StaticMeshComponent.IsValid())
{
return;
}
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);
#if WITH_EDITOR
StaticMeshComponent->Modify();
StaticMeshComponent->bCustomOverrideVertexColorPerLOD = (MeshLODIndex > 0);
#endif
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
#if WITH_EDITOR
StaticMeshComponent->CachePaintedDataIfNecessary();
StaticMeshComponent->StaticMeshDerivedDataKey = StaticMesh->GetRenderData()->DerivedDataKey;
#endif
}
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<IMeshPaintComponentAdapter> FMeshPaintStaticMeshComponentAdapterFactory::Construct(class UMeshComponent* InComponent, int32 InMeshLODIndex) const
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(InComponent))
{
if (StaticMeshComponent->GetStaticMesh() != nullptr)
{
TSharedRef<FMeshPaintStaticMeshComponentAdapter> Result = MakeShareable(new FMeshPaintStaticMeshComponentAdapter());
if (Result->Construct(InComponent, InMeshLODIndex))
{
return Result;
}
}
}
return nullptr;
}