Files
UnrealEngine/Engine/Source/Editor/ClothPainter/Private/ClothMeshAdapter.cpp
2025-05-18 13:04:45 +08:00

361 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ClothMeshAdapter.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "ClothLODData.h"
#include "ClothPhysicalMeshData.h"
#include "ClothingAsset.h"
#include "ClothingAssetBase.h"
#include "Components/MeshComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Containers/Set.h"
#include "Engine/HitResult.h"
#include "Engine/NetSerialization.h"
#include "Engine/SkeletalMesh.h"
#include "HAL/PlatformCrt.h"
#include "Math/Color.h"
#include "Math/Plane.h"
#include "Math/Transform.h"
#include "Math/Vector.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Guid.h"
#include "Serialization/StructuredArchiveAdapters.h"
#include "Templates/Casts.h"
#include "UObject/ObjectPtr.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class IMeshPaintGeometryAdapter;
class UTexture;
TSharedPtr<IMeshPaintGeometryAdapter> FClothMeshPaintAdapterFactory::Construct(UMeshComponent* InComponent, int32 InPaintingMeshLODIndex) const
{
if (USkeletalMeshComponent* Component = Cast<USkeletalMeshComponent>(InComponent))
{
if (Component->GetSkeletalMeshAsset() != nullptr)
{
TSharedRef<FClothMeshPaintAdapter> Result = MakeShareable(new FClothMeshPaintAdapter());
if (Result->Construct(InComponent, InPaintingMeshLODIndex))
{
return Result;
}
}
}
return nullptr;
}
bool FClothMeshPaintAdapter::Construct(UMeshComponent* InComponent, int32 InPaintingClothLODIndex)
{
SkeletalMeshComponent = Cast<USkeletalMeshComponent>(InComponent);
if (SkeletalMeshComponent != nullptr)
{
if (SkeletalMeshComponent->GetSkeletalMeshAsset() != nullptr)
{
ReferencedSkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
PaintingClothLODIndex = InPaintingClothLODIndex;
PaintingClothMaskIndex = INDEX_NONE;
const bool bSuccess = Initialize();
return bSuccess;
}
}
return false;
}
bool FClothMeshPaintAdapter::Initialize()
{
check(ReferencedSkeletalMesh == SkeletalMeshComponent->GetSkeletalMeshAsset());
bool bHaveAsset = false;
bool bBaseInit = false;
UClothingAssetCommon* ClothingAsset = Cast<UClothingAssetCommon>(SelectedAsset);
bHaveAsset = ClothingAsset && ClothingAsset->LodData.IsValidIndex(PaintingClothLODIndex);
bBaseInit = FBaseMeshPaintGeometryAdapter::Initialize();
return bHaveAsset && bBaseInit;
}
bool ClothSegmentTriangleIntersection(const FVector& StartPoint, const FVector& EndPoint, const FVector& A, const FVector& B, const FVector& C, FVector& OutIntersectPoint, FVector& OutTriangleNormal)
{
const FVector BA = A - B;
const FVector CB = B - C;
const FVector TriNormal = BA ^ CB;
bool bCollide = FMath::SegmentPlaneIntersection(StartPoint, EndPoint, FPlane(A, TriNormal), OutIntersectPoint);
if (!bCollide)
{
return false;
}
FVector BaryCentric = FMath::ComputeBaryCentric2D(OutIntersectPoint, A, B, C);
if (BaryCentric.X > 0.0f && BaryCentric.Y > 0.0f && BaryCentric.Z > 0.0f)
{
OutTriangleNormal = TriNormal;
return true;
}
return false;
}
bool FClothMeshPaintAdapter::LineTraceComponent(struct FHitResult& OutHit, const FVector Start, const FVector End, const struct FCollisionQueryParams& Params) const
{
const int32 NumTriangles = MeshIndices.Num() / 3;
float MinDistance = FLT_MAX;
FVector Intersect;
FVector Normal;
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
FVector IntersectPoint;
FVector HitNormal;
bool bHit = ClothSegmentTriangleIntersection(Start, End, MeshVertices[MeshIndices[(TriangleIndex * 3) + 0]], MeshVertices[MeshIndices[(TriangleIndex * 3) + 1]], MeshVertices[MeshIndices[(TriangleIndex * 3) + 2]], IntersectPoint, HitNormal);
if (bHit)
{
const float Distance = (Start - IntersectPoint).SizeSquared();
if (Distance < MinDistance)
{
MinDistance = Distance;
Intersect = IntersectPoint;
Normal = HitNormal;
}
}
}
if (MinDistance != FLT_MAX)
{
OutHit.Component = SkeletalMeshComponent;
OutHit.Normal = Normal.GetSafeNormal();
OutHit.Location = Intersect;
OutHit.bBlockingHit = true;
return true;
}
return false;
}
void FClothMeshPaintAdapter::QueryPaintableTextures(int32 MaterialIndex, int32& OutDefaultIndex, TArray<struct FPaintableTexture>& InOutTextureList)
{
}
void FClothMeshPaintAdapter::ApplyOrRemoveTextureOverride(UTexture* SourceTexture, UTexture* OverrideTexture) const
{
}
void FClothMeshPaintAdapter::PreEdit()
{
ReferencedSkeletalMesh->Modify();
SelectedAsset->Modify();
}
void FClothMeshPaintAdapter::PostEdit()
{
}
void FClothMeshPaintAdapter::GetVertexColor(int32 VertexIndex, FColor& OutColor, bool bInstance /*= true*/) const
{
checkf(false, TEXT("Not supported, should not be vertex painting cloth"));
}
void FClothMeshPaintAdapter::SetVertexColor(int32 VertexIndex, FColor Color, bool bInstance /*= true*/)
{
checkf(false, TEXT("Not supported, should not be vertex painting cloth"));
}
void FClothMeshPaintAdapter::GetTextureCoordinate(int32 VertexIndex, int32 ChannelIndex, FVector2D& OutTextureCoordinate) const
{
checkf(false, TEXT("Not supported"));
}
FMatrix FClothMeshPaintAdapter::GetComponentToWorldMatrix() const
{
return SkeletalMeshComponent->GetComponentToWorld().ToMatrixWithScale();
}
TArray<FVector> FClothMeshPaintAdapter::SphereIntersectVertices(const float ComponentSpaceSquaredBrushRadius, const FVector& ComponentSpaceBrushPosition, const FVector& ComponentSpaceCameraPosition, const bool bOnlyFrontFacing) const
{
// Get list of intersecting triangles with given sphere data
const TArray<uint32> IntersectedTriangles = SphereIntersectTriangles(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, bOnlyFrontFacing);
// Get a list of unique vertices indexed by the influenced triangles
TSet<int32> InfluencedVertices;
InfluencedVertices.Reserve(IntersectedTriangles.Num());
for (int32 IntersectedTriangle : IntersectedTriangles)
{
InfluencedVertices.Add(MeshIndices[IntersectedTriangle * 3 + 0]);
InfluencedVertices.Add(MeshIndices[IntersectedTriangle * 3 + 2]);
InfluencedVertices.Add(MeshIndices[IntersectedTriangle * 3 + 1]);
}
TArray<FVector> InRangeVertices;
InRangeVertices.Empty(MeshVertices.Num());
for (int32 VertexIndex : InfluencedVertices)
{
const FVector& Vertex = MeshVertices[VertexIndex];
if (FVector::DistSquared(ComponentSpaceBrushPosition, Vertex) <= ComponentSpaceSquaredBrushRadius)
{
InRangeVertices.Add(Vertex);
}
}
return InRangeVertices;
}
void FClothMeshPaintAdapter::SetSelectedClothingAsset(const FGuid& InAssetGuid, int32 InAssetLod, int32 InMaskIndex)
{
SelectedAsset = nullptr;
if(InAssetGuid.IsValid() && ReferencedSkeletalMesh)
{
for(UClothingAssetBase* Asset : ReferencedSkeletalMesh->GetMeshClothingAssets())
{
UClothingAssetCommon* ConcreteAsset = CastChecked<UClothingAssetCommon>(Asset);
if(ConcreteAsset->GetAssetGuid() == InAssetGuid)
{
if(ConcreteAsset->IsValidLod(InAssetLod))
{
FClothLODDataCommon& LodData = ConcreteAsset->LodData[InAssetLod];
if(LodData.PointWeightMaps.IsValidIndex(InMaskIndex))
{
PaintingClothLODIndex = InAssetLod;
PaintingClothMaskIndex = InMaskIndex;
SelectedAsset = Asset;
}
}
break;
}
}
}
if(SelectedAsset)
{
Initialize();
}
}
const TArray<int32>* FClothMeshPaintAdapter::GetVertexNeighbors(int32 InVertexIndex) const
{
for(const FClothAssetInfo& Info : AssetInfoMap)
{
if(InVertexIndex >= Info.VertexStart && InVertexIndex < Info.VertexEnd)
{
return &Info.NeighborMap[InVertexIndex - Info.VertexStart];
}
}
return nullptr;
}
bool FClothMeshPaintAdapter::InitializeVertexData()
{
int32 VertexOffset = 0;
int32 IndexOffset = 0;
AssetInfoMap.Empty();
MeshVertices.Empty();
MeshIndices.Empty();
if (SelectedAsset)
{
if(UDebugSkelMeshComponent* DebugComponent = Cast<UDebugSkelMeshComponent>(SkeletalMeshComponent))
{
if(DebugComponent->SkinnedSelectedClothingPositions.Num() > 0)
{
UClothingAssetCommon* const ConcreteAsset = CastChecked<UClothingAssetCommon>(SelectedAsset);
if (ConcreteAsset->LodData.IsValidIndex(PaintingClothLODIndex))
{
const FClothLODDataCommon& LODData = ConcreteAsset->LodData[PaintingClothLODIndex];
const FClothPhysicalMeshData& MeshData = LODData.PhysicalMeshData;
MeshVertices.Append(DebugComponent->SkinnedSelectedClothingPositions);
MeshIndices.Append(MeshData.Indices);
for(int32 Index = IndexOffset; Index < MeshIndices.Num(); ++Index)
{
MeshIndices[Index] += VertexOffset;
}
FClothAssetInfo Info;
Info.IndexStart = IndexOffset;
Info.VertexStart = VertexOffset;
IndexOffset += MeshData.Indices.Num();
VertexOffset += MeshData.Vertices.Num();
Info.IndexEnd = IndexOffset;
Info.VertexEnd = VertexOffset;
Info.Asset = ConcreteAsset;
// Set up the edge map / neighbor map
// Pre fill the map, 1 per index
Info.NeighborMap.AddDefaulted(Info.VertexEnd - Info.VertexStart);
// Fill in neighbors defined by triangles
const int32 NumTris = MeshIndices.Num() / 3;
for(int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
const int32 I0 = MeshIndices[TriIndex * 3];
const int32 I1 = MeshIndices[TriIndex * 3 + 1];
const int32 I2 = MeshIndices[TriIndex * 3 + 2];
TArray<int32>& I0Neighbors = Info.NeighborMap[I0];
TArray<int32>& I1Neighbors = Info.NeighborMap[I1];
TArray<int32>& I2Neighbors = Info.NeighborMap[I2];
I0Neighbors.AddUnique(I1);
I0Neighbors.AddUnique(I2);
I1Neighbors.AddUnique(I0);
I1Neighbors.AddUnique(I2);
I2Neighbors.AddUnique(I0);
I2Neighbors.AddUnique(I1);
}
AssetInfoMap.Add(Info);
}
}
}
}
return true;
}
FPointWeightMap* FClothMeshPaintAdapter::GetCurrentMask() const
{
if(HasValidSelection())
{
UClothingAssetCommon* ConcreteAsset = CastChecked<UClothingAssetCommon>(SelectedAsset);
return &ConcreteAsset->LodData[PaintingClothLODIndex].PointWeightMaps[PaintingClothMaskIndex];
}
return nullptr;
}
bool FClothMeshPaintAdapter::HasValidSelection() const
{
if(SelectedAsset)
{
UClothingAssetCommon* ConcreteAsset = Cast<UClothingAssetCommon>(SelectedAsset);
// Only valid selection if we have a valid asset, asset LOD and a mask
if(ConcreteAsset->LodData.IsValidIndex(PaintingClothLODIndex) &&
ConcreteAsset->LodData[PaintingClothLODIndex].PointWeightMaps.IsValidIndex(PaintingClothMaskIndex))
{
return true;
}
}
return false;
}