1807 lines
63 KiB
C++
1807 lines
63 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshPaintHelpers.h"
|
|
|
|
#include "ComponentReregisterContext.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Engine/SkinnedAssetCommon.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "IMeshPaintComponentAdapter.h"
|
|
#include "InteractiveTool.h"
|
|
#include "MeshPaintAdapterFactory.h"
|
|
#include "MeshVertexPaintingTool.h"
|
|
#include "Rendering/SkeletalMeshLODModel.h"
|
|
#include "Rendering/SkeletalMeshModel.h"
|
|
#include "Rendering/SkeletalMeshRenderData.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshComponentLODInfo.h"
|
|
#include "TexturePaintToolset.h"
|
|
#include "VT/MeshPaintVirtualTexture.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(MeshPaintHelpers)
|
|
|
|
extern void PropagateVertexPaintToSkeletalMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex);
|
|
|
|
UMeshPaintingSubsystem::UMeshPaintingSubsystem()
|
|
: bNeedsRecache(true)
|
|
, bSelectionSupportsVertexPaint(false)
|
|
, bSelectionSupportsTextureColorPaint(false)
|
|
, bSelectionSupportsTextureAssetPaint(false)
|
|
{
|
|
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::HasPaintableMesh(UActorComponent* Component)
|
|
{
|
|
return Cast<UMeshComponent>(Component) != nullptr;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::RemoveInstanceVertexColors(UObject* Obj)
|
|
{
|
|
// Currently only static mesh component supports per instance vertex colors so only need to retrieve those and remove colors
|
|
AActor* Actor = Cast<AActor>(Obj);
|
|
if (Actor != nullptr)
|
|
{
|
|
TArray<UStaticMeshComponent*> StaticMeshComponents;
|
|
Actor->GetComponents(StaticMeshComponents);
|
|
for (const auto& StaticMeshComponent : StaticMeshComponents)
|
|
{
|
|
if (StaticMeshComponent != nullptr)
|
|
{
|
|
UMeshPaintingSubsystem::RemoveComponentInstanceVertexColors(StaticMeshComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::RemoveComponentInstanceVertexColors(UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
if (StaticMeshComponent != nullptr && StaticMeshComponent->GetStaticMesh() != nullptr)
|
|
{
|
|
// Mark the mesh component as modified
|
|
StaticMeshComponent->Modify();
|
|
|
|
// If this is called from the Remove button being clicked the SMC wont be in a Reregister context,
|
|
// but when it gets called from a Paste or Copy to Source operation it's already inside a more specific
|
|
// SMCRecreateScene context so we shouldn't put it inside another one.
|
|
if (StaticMeshComponent->IsRenderStateCreated())
|
|
{
|
|
// Detach all instances of this static mesh from the scene.
|
|
FComponentReregisterContext ComponentReregisterContext(StaticMeshComponent);
|
|
|
|
StaticMeshComponent->RemoveInstanceVertexColors();
|
|
}
|
|
else
|
|
{
|
|
StaticMeshComponent->RemoveInstanceVertexColors();
|
|
}
|
|
}
|
|
}
|
|
|
|
UTexture* UMeshPaintingSubsystem::CreateMeshPaintTexture(UObject* Outer, uint32 TextureSize)
|
|
{
|
|
const uint32 AlignedTextureSize = MeshPaintVirtualTexture::GetAlignedTextureSize(TextureSize);
|
|
const uint32 TextureNumMips = FMath::FloorLog2(AlignedTextureSize) + 1;
|
|
|
|
UMeshPaintVirtualTexture* NewTexture = NewObject<UMeshPaintVirtualTexture>(Outer);
|
|
NewTexture->Source.Init(AlignedTextureSize, AlignedTextureSize, 1, TextureNumMips, TSF_BGRA8);
|
|
NewTexture->UpdateResource();
|
|
|
|
return NewTexture;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::CreateComponentMeshPaintTexture(UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
if (StaticMeshComponent != nullptr && StaticMeshComponent->GetMeshPaintTexture() == nullptr && StaticMeshComponent->CanMeshPaintTextureColors())
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
|
|
const uint32 TextureSize = StaticMeshComponent->GetMeshPaintTextureResolution();
|
|
const uint32 TextureNumMips = FMath::FloorLog2(TextureSize) + 1;
|
|
|
|
UMeshPaintVirtualTexture* NewTexture = NewObject<UMeshPaintVirtualTexture>(StaticMeshComponent->GetOutermost());
|
|
NewTexture->Source.Init(TextureSize, TextureSize, 1, TextureNumMips, TSF_BGRA8);
|
|
NewTexture->OwningComponent = MakeWeakObjectPtr(StaticMeshComponent);
|
|
NewTexture->UpdateResource();
|
|
|
|
StaticMeshComponent->SetMeshPaintTexture(NewTexture);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::CreateComponentMeshPaintTexture(UStaticMeshComponent* StaticMeshComponent, FImageView const& InImage)
|
|
{
|
|
if (StaticMeshComponent != nullptr)
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
|
|
UMeshPaintVirtualTexture* NewTexture = NewObject<UMeshPaintVirtualTexture>(StaticMeshComponent->GetOutermost());
|
|
NewTexture->Source.Init(InImage);
|
|
NewTexture->OwningComponent = MakeWeakObjectPtr(StaticMeshComponent);
|
|
NewTexture->UpdateResource();
|
|
|
|
StaticMeshComponent->SetMeshPaintTexture(NewTexture);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::RemoveComponentMeshPaintTexture(UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
if (StaticMeshComponent != nullptr && StaticMeshComponent->GetMeshPaintTexture() != nullptr)
|
|
{
|
|
// Mark the mesh component as modified
|
|
StaticMeshComponent->Modify();
|
|
|
|
StaticMeshComponent->SetMeshPaintTexture(nullptr);
|
|
}
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::PropagateColorsToRawMesh(UStaticMesh* StaticMesh, int32 LODIndex, FStaticMeshComponentLODInfo& ComponentLODInfo)
|
|
{
|
|
if (!ComponentLODInfo.OverrideVertexColors ||
|
|
!StaticMesh ||
|
|
!StaticMesh->IsSourceModelValid(LODIndex) ||
|
|
!StaticMesh->GetRenderData() ||
|
|
!StaticMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex) ||
|
|
StaticMesh->GetOutermost()->bIsCookedForEditor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bPropagatedColors = false;
|
|
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex);
|
|
FStaticMeshRenderData& RenderData = *StaticMesh->GetRenderData();
|
|
FStaticMeshLODResources& RenderModel = RenderData.LODResources[LODIndex];
|
|
FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors;
|
|
if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices())
|
|
{
|
|
// Use the wedge map if it is available as it is lossless.
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
|
|
|
|
if (MeshDescription == nullptr)
|
|
{
|
|
//Cannot propagate to a generated LOD, the generated LOD use the source LOD vertex painting.
|
|
return false;
|
|
}
|
|
|
|
FStaticMeshAttributes Attributes(*MeshDescription);
|
|
int32 NumWedges = MeshDescription->VertexInstances().Num();
|
|
if (RenderModel.WedgeMap.Num() == NumWedges)
|
|
{
|
|
TVertexInstanceAttributesRef<FVector4f> Colors = Attributes.GetVertexInstanceColors();
|
|
int32 VertexInstanceIndex = 0;
|
|
for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
FLinearColor WedgeColor = FLinearColor::White;
|
|
int32 Index = RenderModel.WedgeMap[VertexInstanceIndex];
|
|
if (Index != INDEX_NONE)
|
|
{
|
|
WedgeColor = FLinearColor(ColorVertexBuffer.VertexColor(Index));
|
|
}
|
|
Colors[VertexInstanceID] = WedgeColor;
|
|
VertexInstanceIndex++;
|
|
}
|
|
StaticMesh->CommitMeshDescription(LODIndex);
|
|
bPropagatedColors = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
|
|
// If there's no raw mesh data, don't try to do any fixup here
|
|
if (MeshDescription == nullptr || ComponentLODInfo.OverrideVertexColors == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FStaticMeshAttributes Attributes(*MeshDescription);
|
|
|
|
// Fall back to mapping based on position.
|
|
TVertexAttributesConstRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
|
|
TVertexInstanceAttributesRef<FVector4f> Colors = Attributes.GetVertexInstanceColors();
|
|
TArray<FColor> NewVertexColors;
|
|
FPositionVertexBuffer TempPositionVertexBuffer;
|
|
int32 NumVertex = MeshDescription->Vertices().Num();
|
|
TArray<FVector3f> VertexPositionsDup;
|
|
VertexPositionsDup.AddZeroed(NumVertex);
|
|
int32 VertexIndex = 0;
|
|
for (const FVertexID VertexID : MeshDescription->Vertices().GetElementIDs())
|
|
{
|
|
VertexPositionsDup[VertexIndex++] = VertexPositions[VertexID];
|
|
}
|
|
TempPositionVertexBuffer.Init(VertexPositionsDup);
|
|
RemapPaintedVertexColors(
|
|
ComponentLODInfo.PaintedVertices,
|
|
ComponentLODInfo.OverrideVertexColors,
|
|
RenderModel.VertexBuffers.PositionVertexBuffer,
|
|
RenderModel.VertexBuffers.StaticMeshVertexBuffer,
|
|
TempPositionVertexBuffer,
|
|
/*OptionalVertexBuffer=*/ nullptr,
|
|
NewVertexColors
|
|
);
|
|
if (NewVertexColors.Num() == NumVertex)
|
|
{
|
|
for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
const FVertexID VertexID = MeshDescription->GetVertexInstanceVertex(VertexInstanceID);
|
|
Colors[VertexInstanceID] = FLinearColor(NewVertexColors[VertexID.GetValue()]);
|
|
}
|
|
StaticMesh->CommitMeshDescription(LODIndex);
|
|
bPropagatedColors = true;
|
|
}
|
|
}
|
|
return bPropagatedColors;
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::PaintVertex(const FVector& InVertexPosition, const FMeshPaintParameters& InParams, FColor& InOutVertexColor)
|
|
{
|
|
float SquaredDistanceToVertex2D;
|
|
float VertexDepthToBrush;
|
|
if (UMeshPaintingSubsystem::IsPointInfluencedByBrush(InVertexPosition, InParams, SquaredDistanceToVertex2D, VertexDepthToBrush))
|
|
{
|
|
// Compute amount of paint to apply
|
|
const float PaintAmount = UMeshPaintingSubsystem::ComputePaintMultiplier(SquaredDistanceToVertex2D, InParams.BrushStrength, InParams.InnerBrushRadius, InParams.BrushRadialFalloffRange, InParams.BrushDepth, InParams.BrushDepthFalloffRange, VertexDepthToBrush);
|
|
|
|
const FLinearColor OldColor = InOutVertexColor.ReinterpretAsLinear();
|
|
FLinearColor NewColor = OldColor;
|
|
|
|
InParams.ApplyVertexDataDelegate.Broadcast(InParams, OldColor, NewColor, PaintAmount);
|
|
|
|
// Save the new color
|
|
InOutVertexColor.R = FMath::Clamp(FMath::RoundToInt(NewColor.R * 255.0f), 0, 255);
|
|
InOutVertexColor.G = FMath::Clamp(FMath::RoundToInt(NewColor.G * 255.0f), 0, 255);
|
|
InOutVertexColor.B = FMath::Clamp(FMath::RoundToInt(NewColor.B * 255.0f), 0, 255);
|
|
InOutVertexColor.A = FMath::Clamp(FMath::RoundToInt(NewColor.A * 255.0f), 0, 255);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Out of range
|
|
return false;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ApplyVertexColorPaint(const FMeshPaintParameters &InParams, const FLinearColor &OldColor, FLinearColor &NewColor, const float PaintAmount)
|
|
{
|
|
// Color painting
|
|
|
|
if (InParams.bWriteRed)
|
|
{
|
|
NewColor.R = (OldColor.R < InParams.BrushColor.R) ? FMath::Min(InParams.BrushColor.R, OldColor.R + PaintAmount) : FMath::Max(InParams.BrushColor.R, OldColor.R - PaintAmount);
|
|
}
|
|
|
|
if (InParams.bWriteGreen)
|
|
{
|
|
NewColor.G = (OldColor.G < InParams.BrushColor.G) ? FMath::Min(InParams.BrushColor.G, OldColor.G + PaintAmount) : FMath::Max(InParams.BrushColor.G, OldColor.G - PaintAmount);
|
|
}
|
|
|
|
if (InParams.bWriteBlue)
|
|
{
|
|
NewColor.B = (OldColor.B < InParams.BrushColor.B) ? FMath::Min(InParams.BrushColor.B, OldColor.B + PaintAmount) : FMath::Max(InParams.BrushColor.B, OldColor.B - PaintAmount);
|
|
}
|
|
|
|
if (InParams.bWriteAlpha)
|
|
{
|
|
NewColor.A = (OldColor.A < InParams.BrushColor.A) ? FMath::Min(InParams.BrushColor.A, OldColor.A + PaintAmount) : FMath::Max(InParams.BrushColor.A, OldColor.A - PaintAmount);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ApplyVertexWeightPaint(const FMeshPaintParameters &InParams, const FLinearColor &OldColor, FLinearColor &NewColor, const float PaintAmount)
|
|
{
|
|
// Total number of texture blend weights we're using
|
|
check(InParams.TotalWeightCount > 0);
|
|
check(InParams.TotalWeightCount <= MeshPaintDefs::MaxSupportedWeights);
|
|
|
|
// True if we should assume the last weight index is composed of one minus the sum of all
|
|
// of the other weights. This effectively allows an additional weight with no extra memory
|
|
// used, but potentially requires extra pixel shader instructions to render.
|
|
//
|
|
// NOTE: If you change the default here, remember to update the MeshPaintWindow UI and strings
|
|
//
|
|
// NOTE: Materials must be authored to match the following assumptions!
|
|
const bool bUsingOneMinusTotal =
|
|
InParams.TotalWeightCount == 2 || // Two textures: Use a lerp() in pixel shader (single value)
|
|
InParams.TotalWeightCount == 5; // Five texture: Requires 1.0-sum( R+G+B+A ) in shader
|
|
check(bUsingOneMinusTotal || InParams.TotalWeightCount <= MeshPaintDefs::MaxSupportedPhysicalWeights);
|
|
|
|
// Prefer to use RG/RGB instead of AR/ARG when we're only using 2/3 physical weights
|
|
const int32 TotalPhysicalWeights = bUsingOneMinusTotal ? InParams.TotalWeightCount - 1 : InParams.TotalWeightCount;
|
|
const bool bUseColorAlpha =
|
|
TotalPhysicalWeights != 2 && // Two physical weights: Use RG instead of AR
|
|
TotalPhysicalWeights != 3; // Three physical weights: Use RGB instead of ARG
|
|
|
|
// Index of the blend weight that we're painting
|
|
check(InParams.PaintWeightIndex >= 0 && InParams.PaintWeightIndex < MeshPaintDefs::MaxSupportedWeights);
|
|
|
|
// Convert the color value to an array of weights
|
|
float Weights[MeshPaintDefs::MaxSupportedWeights];
|
|
{
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < InParams.TotalWeightCount; ++CurWeightIndex)
|
|
{
|
|
if (CurWeightIndex == TotalPhysicalWeights)
|
|
{
|
|
// This weight's value is one minus the sum of all previous weights
|
|
float OtherWeightsTotal = 0.0f;
|
|
for (int32 OtherWeightIndex = 0; OtherWeightIndex < CurWeightIndex; ++OtherWeightIndex)
|
|
{
|
|
OtherWeightsTotal += Weights[OtherWeightIndex];
|
|
}
|
|
Weights[CurWeightIndex] = 1.0f - OtherWeightsTotal;
|
|
}
|
|
else
|
|
{
|
|
switch (CurWeightIndex)
|
|
{
|
|
case 0:
|
|
Weights[CurWeightIndex] = bUseColorAlpha ? OldColor.A : OldColor.R;
|
|
break;
|
|
|
|
case 1:
|
|
Weights[CurWeightIndex] = bUseColorAlpha ? OldColor.R : OldColor.G;
|
|
break;
|
|
|
|
case 2:
|
|
Weights[CurWeightIndex] = bUseColorAlpha ? OldColor.G : OldColor.B;
|
|
break;
|
|
|
|
case 3:
|
|
check(bUseColorAlpha);
|
|
Weights[CurWeightIndex] = OldColor.B;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go ahead any apply paint!
|
|
Weights[InParams.PaintWeightIndex] += PaintAmount;
|
|
Weights[InParams.PaintWeightIndex] = FMath::Clamp(Weights[InParams.PaintWeightIndex], 0.0f, 1.0f);
|
|
|
|
|
|
// Now renormalize all of the other weights
|
|
float OtherWeightsTotal = 0.0f;
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < InParams.TotalWeightCount; ++CurWeightIndex)
|
|
{
|
|
if (CurWeightIndex != InParams.PaintWeightIndex)
|
|
{
|
|
OtherWeightsTotal += Weights[CurWeightIndex];
|
|
}
|
|
}
|
|
const float NormalizeTarget = 1.0f - Weights[InParams.PaintWeightIndex];
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < InParams.TotalWeightCount; ++CurWeightIndex)
|
|
{
|
|
if (CurWeightIndex != InParams.PaintWeightIndex)
|
|
{
|
|
if (OtherWeightsTotal == 0.0f)
|
|
{
|
|
Weights[CurWeightIndex] = NormalizeTarget / (InParams.TotalWeightCount - 1);
|
|
}
|
|
else
|
|
{
|
|
Weights[CurWeightIndex] = Weights[CurWeightIndex] / OtherWeightsTotal * NormalizeTarget;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The total of the weights should now always equal 1.0
|
|
float WeightsTotal = 0.0f;
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < InParams.TotalWeightCount; ++CurWeightIndex)
|
|
{
|
|
WeightsTotal += Weights[CurWeightIndex];
|
|
}
|
|
check(FMath::IsNearlyEqual(WeightsTotal, 1.0f, 0.01f));
|
|
|
|
// Convert the weights back to a color value
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < InParams.TotalWeightCount; ++CurWeightIndex)
|
|
{
|
|
// We can skip the non-physical weights as it's already baked into the others
|
|
if (CurWeightIndex != TotalPhysicalWeights)
|
|
{
|
|
switch (CurWeightIndex)
|
|
{
|
|
case 0:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.A = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.R = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.R = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.G = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.G = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.B = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
NewColor.B = Weights[CurWeightIndex];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FLinearColor UMeshPaintingSubsystem::GenerateColorForTextureWeight(const int32 NumWeights, const int32 WeightIndex)
|
|
{
|
|
const bool bUsingOneMinusTotal =
|
|
NumWeights == 2 || // Two textures: Use a lerp() in pixel shader (single value)
|
|
NumWeights == 5; // Five texture: Requires 1.0-sum( R+G+B+A ) in shader
|
|
check(bUsingOneMinusTotal || NumWeights <= MeshPaintDefs::MaxSupportedPhysicalWeights);
|
|
|
|
// Prefer to use RG/RGB instead of AR/ARG when we're only using 2/3 physical weights
|
|
const int32 TotalPhysicalWeights = bUsingOneMinusTotal ? NumWeights - 1 : NumWeights;
|
|
const bool bUseColorAlpha =
|
|
TotalPhysicalWeights != 2 && // Two physical weights: Use RG instead of AR
|
|
TotalPhysicalWeights != 3; // Three physical weights: Use RGB instead of ARG
|
|
|
|
// Index of the blend weight that we're painting
|
|
check(WeightIndex >= 0 && WeightIndex < MeshPaintDefs::MaxSupportedWeights);
|
|
|
|
// Convert the color value to an array of weights
|
|
float Weights[MeshPaintDefs::MaxSupportedWeights];
|
|
{
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < NumWeights; ++CurWeightIndex)
|
|
{
|
|
if (CurWeightIndex == TotalPhysicalWeights)
|
|
{
|
|
// This weight's value is one minus the sum of all previous weights
|
|
float OtherWeightsTotal = 0.0f;
|
|
for (int32 OtherWeightIndex = 0; OtherWeightIndex < CurWeightIndex; ++OtherWeightIndex)
|
|
{
|
|
OtherWeightsTotal += Weights[OtherWeightIndex];
|
|
}
|
|
Weights[CurWeightIndex] = 1.0f - OtherWeightsTotal;
|
|
}
|
|
else
|
|
{
|
|
if (CurWeightIndex == WeightIndex)
|
|
{
|
|
Weights[CurWeightIndex] = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
Weights[CurWeightIndex] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FLinearColor NewColor(FLinearColor::Black);
|
|
// Convert the weights back to a color value
|
|
for (int32 CurWeightIndex = 0; CurWeightIndex < NumWeights; ++CurWeightIndex)
|
|
{
|
|
// We can skip the non-physical weights as it's already baked into the others
|
|
if (CurWeightIndex != TotalPhysicalWeights)
|
|
{
|
|
switch (CurWeightIndex)
|
|
{
|
|
case 0:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.A = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.R = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.R = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.G = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (bUseColorAlpha)
|
|
{
|
|
NewColor.G = Weights[CurWeightIndex];
|
|
}
|
|
else
|
|
{
|
|
NewColor.B = Weights[CurWeightIndex];
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
NewColor.B = Weights[CurWeightIndex];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NewColor;
|
|
}
|
|
|
|
float UMeshPaintingSubsystem::ComputePaintMultiplier(float SquaredDistanceToVertex2D, float BrushStrength, float BrushInnerRadius, float BrushRadialFalloff, float BrushInnerDepth, float BrushDepthFallof, float VertexDepthToBrush)
|
|
{
|
|
float PaintAmount = 1.0f;
|
|
|
|
// Compute the actual distance
|
|
float DistanceToVertex2D = 0.0f;
|
|
if (SquaredDistanceToVertex2D > KINDA_SMALL_NUMBER)
|
|
{
|
|
DistanceToVertex2D = FMath::Sqrt(SquaredDistanceToVertex2D);
|
|
}
|
|
|
|
// Apply radial-based falloff
|
|
if (DistanceToVertex2D > BrushInnerRadius)
|
|
{
|
|
const float RadialBasedFalloff = (DistanceToVertex2D - BrushInnerRadius) / BrushRadialFalloff;
|
|
PaintAmount *= 1.0f - RadialBasedFalloff;
|
|
}
|
|
|
|
// Apply depth-based falloff
|
|
if (VertexDepthToBrush > BrushInnerDepth)
|
|
{
|
|
const float DepthBasedFalloff = (VertexDepthToBrush - BrushInnerDepth) / BrushDepthFallof;
|
|
PaintAmount *= 1.0f - DepthBasedFalloff;
|
|
}
|
|
|
|
PaintAmount *= BrushStrength;
|
|
|
|
return PaintAmount;
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::IsPointInfluencedByBrush(const FVector& InPosition, const FMeshPaintParameters& InParams, float& OutSquaredDistanceToVertex2D, float& OutVertexDepthToBrush)
|
|
{
|
|
// Project the vertex into the plane of the brush
|
|
FVector BrushSpaceVertexPosition = InParams.InverseBrushToWorldMatrix.TransformPosition(InPosition);
|
|
FVector2D BrushSpaceVertexPosition2D(BrushSpaceVertexPosition.X, BrushSpaceVertexPosition.Y);
|
|
|
|
// Is the brush close enough to the vertex to paint?
|
|
const float SquaredDistanceToVertex2D = BrushSpaceVertexPosition2D.SizeSquared();
|
|
if (SquaredDistanceToVertex2D <= InParams.SquaredBrushRadius)
|
|
{
|
|
// OK the vertex is overlapping the brush in 2D space, but is it too close or
|
|
// two far (depth wise) to be influenced?
|
|
const float VertexDepthToBrush = FMath::Abs(BrushSpaceVertexPosition.Z);
|
|
if (VertexDepthToBrush <= InParams.BrushDepth)
|
|
{
|
|
OutSquaredDistanceToVertex2D = SquaredDistanceToVertex2D;
|
|
OutVertexDepthToBrush = VertexDepthToBrush;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::IsPointInfluencedByBrush(const FVector2D& BrushSpacePosition, const float BrushRadius, float& OutInRangeValue)
|
|
{
|
|
const float DistanceToBrush = BrushSpacePosition.SizeSquared();
|
|
if (DistanceToBrush <= BrushRadius)
|
|
{
|
|
OutInRangeValue = DistanceToBrush / BrushRadius;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32 UMeshPaintingSubsystem::GetVertexColorBufferSize(UMeshComponent* MeshComponent, int32 LODIndex, bool bInstance)
|
|
{
|
|
checkf(MeshComponent != nullptr, TEXT("Invalid static mesh component ptr"));
|
|
|
|
uint32 SizeInBytes = 0;
|
|
|
|
// Retrieve component instance vertex color buffer size
|
|
if (bInstance)
|
|
{
|
|
if(UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
|
|
{
|
|
if (StaticMeshComponent->LODData.IsValidIndex(LODIndex))
|
|
{
|
|
const FStaticMeshComponentLODInfo& InstanceMeshLODInfo = StaticMeshComponent->LODData[LODIndex];
|
|
if (InstanceMeshLODInfo.OverrideVertexColors)
|
|
{
|
|
SizeInBytes = InstanceMeshLODInfo.OverrideVertexColors->GetAllocatedSize();
|
|
}
|
|
}
|
|
}
|
|
else if (USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent))
|
|
{
|
|
if (SkinnedMeshComponent->LODInfo.IsValidIndex(LODIndex))
|
|
{
|
|
const FSkelMeshComponentLODInfo& LODInfo = SkinnedMeshComponent->LODInfo[LODIndex];
|
|
if (LODInfo.OverrideVertexColors)
|
|
{
|
|
SizeInBytes = LODInfo.OverrideVertexColors->GetAllocatedSize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Retrieve static mesh asset vertex color buffer size
|
|
else
|
|
{
|
|
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
|
|
{
|
|
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
checkf(StaticMesh != nullptr, TEXT("Invalid static mesh ptr"));
|
|
if (StaticMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex))
|
|
{
|
|
// count the base mesh color data
|
|
FStaticMeshLODResources& LODModel = StaticMesh->GetRenderData()->LODResources[LODIndex];
|
|
SizeInBytes = LODModel.VertexBuffers.ColorVertexBuffer.GetAllocatedSize();
|
|
}
|
|
}
|
|
else if (USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent))
|
|
{
|
|
FSkeletalMeshRenderData* RenderData = SkinnedMeshComponent->GetSkeletalMeshRenderData();
|
|
if (RenderData && RenderData->LODRenderData.IsValidIndex(LODIndex))
|
|
{
|
|
SizeInBytes = RenderData->LODRenderData[LODIndex].StaticVertexBuffers.ColorVertexBuffer.GetAllocatedSize();
|
|
}
|
|
}
|
|
}
|
|
|
|
return SizeInBytes;
|
|
}
|
|
|
|
uint32 UMeshPaintingSubsystem::GetMeshPaintTextureResourceSize(UMeshComponent* MeshComponent)
|
|
{
|
|
if (UTexture* Texture = MeshComponent->GetMeshPaintTexture())
|
|
{
|
|
// Check that the texture has finished compilation before reading platform data.
|
|
if (!Texture->IsDefaultTexture())
|
|
{
|
|
FTexturePlatformData** PlatformDataPtr = Texture->GetRunningPlatformData();
|
|
if (PlatformDataPtr != nullptr && *PlatformDataPtr != nullptr)
|
|
{
|
|
return (*PlatformDataPtr)->GetPayloadSize(0);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TArray<FVector> UMeshPaintingSubsystem::GetVerticesForLOD( const UStaticMesh* StaticMesh, int32 LODIndex)
|
|
{
|
|
checkf(StaticMesh != nullptr, TEXT("Invalid static mesh ptr"));
|
|
|
|
// Retrieve mesh vertices from Static mesh render data
|
|
TArray<FVector> Vertices;
|
|
if (StaticMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex))
|
|
{
|
|
const FStaticMeshLODResources& LODModel = StaticMesh->GetRenderData()->LODResources[LODIndex];
|
|
const FPositionVertexBuffer* VertexBuffer = &LODModel.VertexBuffers.PositionVertexBuffer;
|
|
const uint32 NumVertices = VertexBuffer->GetNumVertices();
|
|
for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
Vertices.Add((FVector)VertexBuffer->VertexPosition(VertexIndex));
|
|
}
|
|
}
|
|
return Vertices;
|
|
}
|
|
|
|
TArray<FColor> UMeshPaintingSubsystem::GetColorDataForLOD( const UStaticMesh* StaticMesh, int32 LODIndex)
|
|
{
|
|
checkf(StaticMesh != nullptr, TEXT("Invalid static mesh ptr"));
|
|
// Retrieve mesh vertex colors from Static mesh render data
|
|
TArray<FColor> Colors;
|
|
if (StaticMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex))
|
|
{
|
|
const FStaticMeshLODResources& LODModel = StaticMesh->GetRenderData()->LODResources[LODIndex];
|
|
const FColorVertexBuffer& ColorBuffer = LODModel.VertexBuffers.ColorVertexBuffer;
|
|
const uint32 NumColors = ColorBuffer.GetNumVertices();
|
|
for (uint32 ColorIndex = 0; ColorIndex < NumColors; ++ColorIndex)
|
|
{
|
|
Colors.Add(ColorBuffer.VertexColor(ColorIndex));
|
|
}
|
|
}
|
|
return Colors;
|
|
}
|
|
|
|
TArray<FColor> UMeshPaintingSubsystem::GetInstanceColorDataForLOD(const UStaticMeshComponent* MeshComponent, int32 LODIndex)
|
|
{
|
|
checkf(MeshComponent != nullptr, TEXT("Invalid static mesh component ptr"));
|
|
TArray<FColor> Colors;
|
|
|
|
// Retrieve mesh vertex colors from Static Mesh component instance data
|
|
if (MeshComponent->LODData.IsValidIndex(LODIndex))
|
|
{
|
|
const FStaticMeshComponentLODInfo& ComponentLODInfo = MeshComponent->LODData[LODIndex];
|
|
const FColorVertexBuffer* ColorBuffer = ComponentLODInfo.OverrideVertexColors;
|
|
if (ColorBuffer)
|
|
{
|
|
const uint32 NumColors = ColorBuffer->GetNumVertices();
|
|
for (uint32 ColorIndex = 0; ColorIndex < NumColors; ++ColorIndex)
|
|
{
|
|
Colors.Add(ColorBuffer->VertexColor(ColorIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Colors;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::SetInstanceColorDataForLOD(UStaticMeshComponent* MeshComponent, int32 LODIndex, const TArray<FColor>& Colors)
|
|
{
|
|
checkf(MeshComponent != nullptr, TEXT("Invalid static mesh component ptr"));
|
|
|
|
const UStaticMesh* Mesh = MeshComponent->GetStaticMesh();
|
|
if (Mesh)
|
|
{
|
|
const FStaticMeshLODResources& RenderData = Mesh->GetRenderData()->LODResources[LODIndex];
|
|
FStaticMeshComponentLODInfo& ComponentLodInfo = MeshComponent->LODData[LODIndex];
|
|
|
|
// First release existing buffer
|
|
if (ComponentLodInfo.OverrideVertexColors)
|
|
{
|
|
ComponentLodInfo.ReleaseOverrideVertexColorsAndBlock();
|
|
}
|
|
|
|
// If we are adding colors to LOD > 0 we flag the component to have per-lod painted mesh colors
|
|
if (LODIndex > 0)
|
|
{
|
|
MeshComponent->bCustomOverrideVertexColorPerLOD = true;
|
|
}
|
|
|
|
// Initialize vertex buffer from given colors
|
|
ComponentLodInfo.OverrideVertexColors = new FColorVertexBuffer;
|
|
ComponentLodInfo.OverrideVertexColors->InitFromColorArray(Colors);
|
|
|
|
//Update the cache painted vertices
|
|
ComponentLodInfo.PaintedVertices.Empty();
|
|
MeshComponent->CachePaintedDataIfNecessary();
|
|
|
|
BeginInitResource(ComponentLodInfo.OverrideVertexColors);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::SetInstanceColorDataForLOD(UStaticMeshComponent* MeshComponent, int32 LODIndex, const FColor FillColor, const FColor MaskColor )
|
|
{
|
|
checkf(MeshComponent != nullptr, TEXT("Invalid static mesh component ptr"));
|
|
|
|
const UStaticMesh* Mesh = MeshComponent->GetStaticMesh();
|
|
if (Mesh)
|
|
{
|
|
const FStaticMeshLODResources& RenderData = Mesh->GetRenderData()->LODResources[LODIndex];
|
|
// Ensure we have enough LOD data structs
|
|
MeshComponent->SetLODDataCount(LODIndex + 1, MeshComponent->LODData.Num());
|
|
FStaticMeshComponentLODInfo& ComponentLodInfo = MeshComponent->LODData[LODIndex];
|
|
|
|
if (MaskColor == FColor::White)
|
|
{
|
|
// First release existing buffer
|
|
if (ComponentLodInfo.OverrideVertexColors)
|
|
{
|
|
ComponentLodInfo.ReleaseOverrideVertexColorsAndBlock();
|
|
}
|
|
|
|
// If we are adding colors to LOD > 0 we flag the component to have per-lod painted mesh colors
|
|
if (LODIndex > 0)
|
|
{
|
|
MeshComponent->bCustomOverrideVertexColorPerLOD = true;
|
|
}
|
|
|
|
// Initialize vertex buffer from given color
|
|
ComponentLodInfo.OverrideVertexColors = new FColorVertexBuffer;
|
|
ComponentLodInfo.OverrideVertexColors->InitFromSingleColor(FillColor, RenderData.GetNumVertices());
|
|
}
|
|
else
|
|
{
|
|
const FStaticMeshLODResources& LODModel = MeshComponent->GetStaticMesh()->GetRenderData()->LODResources[LODIndex];
|
|
/** If there is an actual mask apply it to Fill Color when changing the per-vertex color */
|
|
if (ComponentLodInfo.OverrideVertexColors)
|
|
{
|
|
const uint32 NumVertices = ComponentLodInfo.OverrideVertexColors->GetNumVertices();
|
|
for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
ApplyFillWithMask(ComponentLodInfo.OverrideVertexColors->VertexColor(VertexIndex), MaskColor, FillColor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Initialize vertex buffer from given color
|
|
ComponentLodInfo.OverrideVertexColors = new FColorVertexBuffer;
|
|
FColor NewFillColor(EForceInit::ForceInitToZero);
|
|
ApplyFillWithMask(NewFillColor, MaskColor, FillColor);
|
|
ComponentLodInfo.OverrideVertexColors->InitFromSingleColor(NewFillColor, RenderData.GetNumVertices());
|
|
}
|
|
}
|
|
|
|
//Update the cache painted vertices
|
|
ComponentLodInfo.PaintedVertices.Empty();
|
|
MeshComponent->CachePaintedDataIfNecessary();
|
|
|
|
BeginInitResource(ComponentLodInfo.OverrideVertexColors);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::FillStaticMeshVertexColors(UStaticMeshComponent* MeshComponent, int32 LODIndex, const FColor FillColor, const FColor MaskColor)
|
|
{
|
|
UStaticMesh* Mesh = MeshComponent->GetStaticMesh();
|
|
if (Mesh)
|
|
{
|
|
const int32 NumLODs = Mesh->GetNumLODs();
|
|
if (LODIndex < NumLODs)
|
|
{
|
|
if (LODIndex == -1)
|
|
{
|
|
for (LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
UMeshPaintingSubsystem::SetInstanceColorDataForLOD(MeshComponent, LODIndex, FillColor, MaskColor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UMeshPaintingSubsystem::SetInstanceColorDataForLOD(MeshComponent, LODIndex, FillColor, MaskColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::FillSkeletalMeshVertexColors(USkeletalMeshComponent* MeshComponent, int32 LODIndex, const FColor FillColor, const FColor MaskColor)
|
|
{
|
|
TUniquePtr< FSkinnedMeshComponentRecreateRenderStateContext > RecreateRenderStateContext;
|
|
USkeletalMesh* Mesh = MeshComponent->GetSkeletalMeshAsset();
|
|
if (Mesh)
|
|
{
|
|
// Dirty the mesh
|
|
Mesh->SetFlags(RF_Transactional);
|
|
Mesh->Modify();
|
|
Mesh->SetHasVertexColors(true);
|
|
Mesh->SetVertexColorGuid(FGuid::NewGuid());
|
|
|
|
// Release the static mesh's resources.
|
|
Mesh->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.
|
|
Mesh->ReleaseResourcesFence.Wait();
|
|
|
|
const int32 NumLODs = Mesh->GetLODNum();
|
|
if (NumLODs > 0)
|
|
{
|
|
RecreateRenderStateContext = MakeUnique<FSkinnedMeshComponentRecreateRenderStateContext>(Mesh);
|
|
// TODO: Apply to LODIndex only (or all if set to -1). This requires some extra refactoring
|
|
// because currently all LOD data is being released above.
|
|
for (LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
UMeshPaintingSubsystem::SetColorDataForLOD(Mesh, LODIndex, FillColor, MaskColor);
|
|
PropagateVertexPaintToSkeletalMesh(Mesh, LODIndex);
|
|
}
|
|
Mesh->InitResources();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::SetColorDataForLOD(USkeletalMesh* SkeletalMesh, int32 LODIndex, const FColor FillColor, const FColor MaskColor )
|
|
{
|
|
checkf(SkeletalMesh != nullptr, TEXT("Invalid Skeletal Mesh Ptr"));
|
|
FSkeletalMeshRenderData* Resource = SkeletalMesh->GetResourceForRendering();
|
|
if (Resource && Resource->LODRenderData.IsValidIndex(LODIndex))
|
|
{
|
|
FSkeletalMeshLODRenderData& LODData = Resource->LODRenderData[LODIndex];
|
|
|
|
if (MaskColor == FColor::White)
|
|
{
|
|
LODData.StaticVertexBuffers.ColorVertexBuffer.InitFromSingleColor(FillColor, LODData.GetNumVertices());
|
|
}
|
|
else
|
|
{
|
|
/** If there is an actual mask apply it to Fill Color when changing the per-vertex color */
|
|
const uint32 NumVertices = LODData.StaticVertexBuffers.ColorVertexBuffer.GetNumVertices();
|
|
for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
ApplyFillWithMask(LODData.StaticVertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex), MaskColor, FillColor);
|
|
}
|
|
}
|
|
|
|
BeginInitResource(&LODData.StaticVertexBuffers.ColorVertexBuffer);
|
|
}
|
|
|
|
checkf(SkeletalMesh->GetImportedModel()->LODModels.IsValidIndex(LODIndex), TEXT("Invalid Imported Model index for vertex painting"));
|
|
FSkeletalMeshLODModel& LODModel = SkeletalMesh->GetImportedModel()->LODModels[LODIndex];
|
|
const uint32 NumVertices = LODModel.NumVertices;
|
|
for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
int32 SectionIndex = INDEX_NONE;
|
|
int32 SectionVertexIndex = INDEX_NONE;
|
|
LODModel.GetSectionFromVertexIndex(VertexIndex, SectionIndex, SectionVertexIndex);
|
|
/** If there is an actual mask apply it to Fill Color when changing the per-vertex color */
|
|
ApplyFillWithMask(LODModel.Sections[SectionIndex].SoftVertices[SectionVertexIndex].Color, MaskColor, FillColor);
|
|
}
|
|
|
|
if (!SkeletalMesh->GetLODInfo(LODIndex)->bHasPerLODVertexColors)
|
|
{
|
|
SkeletalMesh->GetLODInfo(LODIndex)->bHasPerLODVertexColors = true;
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ApplyFillWithMask(FColor& InOutColor, const FColor& MaskColor, const FColor& FillColor)
|
|
{
|
|
InOutColor.R = ((InOutColor.R & (~MaskColor.R)) | (FillColor.R & MaskColor.R));
|
|
InOutColor.G = ((InOutColor.G & (~MaskColor.G)) | (FillColor.G & MaskColor.G));
|
|
InOutColor.B = ((InOutColor.B & (~MaskColor.B)) | (FillColor.B & MaskColor.B));
|
|
InOutColor.A = ((InOutColor.A & (~MaskColor.A)) | (FillColor.A & MaskColor.A));
|
|
}
|
|
|
|
|
|
void UMeshPaintingSubsystem::ForceRenderMeshLOD(UMeshComponent* Component, int32 LODIndex)
|
|
{
|
|
// This seems dangerous. What if we save the actor while in forced LOD mode? What if we are stomping an art intended forced LOD?
|
|
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
|
|
{
|
|
StaticMeshComponent->SetForcedLodModel(LODIndex + 1);
|
|
}
|
|
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(Component))
|
|
{
|
|
SkeletalMeshComponent->SetForcedLOD(LODIndex + 1);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ClearMeshTextureOverrides(const IMeshPaintComponentAdapter& GeometryInfo, UMeshComponent* InMeshComponent)
|
|
{
|
|
if (InMeshComponent != nullptr)
|
|
{
|
|
TArray<UTexture*> UsedTextures;
|
|
InMeshComponent->GetUsedTextures(/*out*/ UsedTextures, EMaterialQualityLevel::High);
|
|
|
|
for (UTexture* Texture : UsedTextures)
|
|
{
|
|
if (UTexture2D* Texture2D = Cast<UTexture2D>(Texture))
|
|
{
|
|
GeometryInfo.ApplyOrRemoveTextureOverride(Texture2D, nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ApplyVertexColorsToAllLODs(IMeshPaintComponentAdapter& GeometryInfo, UMeshComponent* InMeshComponent)
|
|
{
|
|
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(InMeshComponent))
|
|
{
|
|
ApplyVertexColorsToAllLODs(GeometryInfo, StaticMeshComponent);
|
|
}
|
|
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(InMeshComponent))
|
|
{
|
|
ApplyVertexColorsToAllLODs(GeometryInfo, SkeletalMeshComponent);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ApplyVertexColorsToAllLODs(IMeshPaintComponentAdapter& GeometryInfo, UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
// If a static mesh component was found, apply LOD0 painting to all lower LODs.
|
|
if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMeshComponent->LODData.Num() < 1)
|
|
{
|
|
//We need at least some painting on the base LOD to apply it to the lower LODs
|
|
return;
|
|
}
|
|
|
|
//Make sure we have something paint in the LOD 0 to apply it to all lower LODs.
|
|
if (StaticMeshComponent->LODData[0].OverrideVertexColors == nullptr && StaticMeshComponent->LODData[0].PaintedVertices.Num() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StaticMeshComponent->bCustomOverrideVertexColorPerLOD = false;
|
|
|
|
uint32 NumLODs = StaticMeshComponent->GetStaticMesh()->GetRenderData()->LODResources.Num();
|
|
StaticMeshComponent->Modify();
|
|
|
|
// Ensure LODData has enough entries in it, free not required.
|
|
StaticMeshComponent->SetLODDataCount(NumLODs, StaticMeshComponent->LODData.Num());
|
|
for (uint32 i = 1; i < NumLODs; ++i)
|
|
{
|
|
FStaticMeshComponentLODInfo* CurrInstanceMeshLODInfo = &StaticMeshComponent->LODData[i];
|
|
FStaticMeshLODResources& CurrRenderData = StaticMeshComponent->GetStaticMesh()->GetRenderData()->LODResources[i];
|
|
// Destroy the instance vertex color array if it doesn't fit
|
|
if (CurrInstanceMeshLODInfo->OverrideVertexColors
|
|
&& CurrInstanceMeshLODInfo->OverrideVertexColors->GetNumVertices() != CurrRenderData.GetNumVertices())
|
|
{
|
|
CurrInstanceMeshLODInfo->ReleaseOverrideVertexColorsAndBlock();
|
|
}
|
|
|
|
if (CurrInstanceMeshLODInfo->OverrideVertexColors)
|
|
{
|
|
CurrInstanceMeshLODInfo->BeginReleaseOverrideVertexColors();
|
|
}
|
|
else
|
|
{
|
|
// Setup the instance vertex color array if we don't have one yet
|
|
CurrInstanceMeshLODInfo->OverrideVertexColors = new FColorVertexBuffer;
|
|
}
|
|
}
|
|
|
|
FlushRenderingCommands();
|
|
|
|
const FStaticMeshComponentLODInfo& SourceCompLODInfo = StaticMeshComponent->LODData[0];
|
|
const FStaticMeshLODResources& SourceRenderData = StaticMeshComponent->GetStaticMesh()->GetRenderData()->LODResources[0];
|
|
for (uint32 i = 1; i < NumLODs; ++i)
|
|
{
|
|
FStaticMeshComponentLODInfo& CurCompLODInfo = StaticMeshComponent->LODData[i];
|
|
FStaticMeshLODResources& CurRenderData = StaticMeshComponent->GetStaticMesh()->GetRenderData()->LODResources[i];
|
|
|
|
check(CurCompLODInfo.OverrideVertexColors);
|
|
check(SourceCompLODInfo.OverrideVertexColors);
|
|
|
|
TArray<FColor> NewOverrideColors;
|
|
|
|
RemapPaintedVertexColors(
|
|
SourceCompLODInfo.PaintedVertices,
|
|
SourceCompLODInfo.OverrideVertexColors,
|
|
SourceRenderData.VertexBuffers.PositionVertexBuffer,
|
|
SourceRenderData.VertexBuffers.StaticMeshVertexBuffer,
|
|
CurRenderData.VertexBuffers.PositionVertexBuffer,
|
|
&CurRenderData.VertexBuffers.StaticMeshVertexBuffer,
|
|
NewOverrideColors
|
|
);
|
|
|
|
if (NewOverrideColors.Num())
|
|
{
|
|
CurCompLODInfo.OverrideVertexColors->InitFromColorArray(NewOverrideColors);
|
|
}
|
|
|
|
// Initialize the vert. colors
|
|
BeginInitResource(CurCompLODInfo.OverrideVertexColors);
|
|
}
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::TryGetNumberOfLODs(const UMeshComponent* MeshComponent, int32& OutNumLODs)
|
|
{
|
|
if (const UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
|
|
{
|
|
const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if (StaticMesh != nullptr)
|
|
{
|
|
OutNumLODs = StaticMesh->GetNumLODs();
|
|
return true;
|
|
}
|
|
}
|
|
else if (const USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(MeshComponent))
|
|
{
|
|
const USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
|
|
if (SkeletalMesh != nullptr)
|
|
{
|
|
OutNumLODs = SkeletalMesh->GetLODNum();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int32 UMeshPaintingSubsystem::GetNumberOfLODs(const UMeshComponent* MeshComponent)
|
|
{
|
|
int32 NumLODs = 1;
|
|
TryGetNumberOfLODs(MeshComponent, NumLODs);
|
|
return NumLODs;
|
|
}
|
|
|
|
int32 UMeshPaintingSubsystem::GetNumberOfUVs(const UMeshComponent* MeshComponent, int32 LODIndex) const
|
|
{
|
|
int32 NumUVs = 0;
|
|
|
|
if (const UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
|
|
{
|
|
const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if (StaticMesh != nullptr && StaticMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex))
|
|
{
|
|
NumUVs = StaticMesh->GetRenderData()->LODResources[LODIndex].GetNumTexCoords();
|
|
}
|
|
}
|
|
else if (const USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(MeshComponent))
|
|
{
|
|
const USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
|
|
if (SkeletalMesh != nullptr && SkeletalMesh->GetResourceForRendering() && SkeletalMesh->GetResourceForRendering()->LODRenderData.IsValidIndex(LODIndex))
|
|
{
|
|
NumUVs = SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].GetNumTexCoords();
|
|
}
|
|
}
|
|
|
|
return NumUVs;
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::DoesMeshComponentContainPerLODColors(const UMeshComponent* MeshComponent)
|
|
{
|
|
bool bPerLODColors = false;
|
|
|
|
if (const UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
|
|
{
|
|
bPerLODColors = StaticMeshComponent->bCustomOverrideVertexColorPerLOD;
|
|
|
|
bool bInstancedLODColors = false;
|
|
if (bPerLODColors)
|
|
{
|
|
|
|
const int32 NumLODs = StaticMeshComponent->LODData.Num();
|
|
|
|
for (int32 LODIndex = 1; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
if (StaticMeshComponent->LODData[LODIndex].PaintedVertices.Num() > 0)
|
|
{
|
|
bInstancedLODColors = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bPerLODColors = bPerLODColors && bInstancedLODColors;
|
|
}
|
|
else if (const USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(MeshComponent))
|
|
{
|
|
if (const USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset())
|
|
{
|
|
// Only check LOD level 1 and above
|
|
for (int32 LODIndex = 1, NumLODs = SkeletalMesh->GetLODNum(); LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
const FSkeletalMeshLODInfo* Info = SkeletalMesh->GetLODInfo(LODIndex);
|
|
if (Info->bHasPerLODVertexColors)
|
|
{
|
|
bPerLODColors = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bPerLODColors;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::GetInstanceColorDataInfo(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, int32& OutTotalInstanceVertexColorBytes)
|
|
{
|
|
checkf(StaticMeshComponent, TEXT("Invalid StaticMeshComponent"));
|
|
OutTotalInstanceVertexColorBytes = 0;
|
|
|
|
const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if (StaticMesh != nullptr && StaticMesh->GetNumLODs() > (int32)LODIndex && StaticMeshComponent->LODData.IsValidIndex(LODIndex))
|
|
{
|
|
// count the instance color data
|
|
const FStaticMeshComponentLODInfo& InstanceMeshLODInfo = StaticMeshComponent->LODData[LODIndex];
|
|
if (InstanceMeshLODInfo.OverrideVertexColors)
|
|
{
|
|
OutTotalInstanceVertexColorBytes += InstanceMeshLODInfo.OverrideVertexColors->GetAllocatedSize();
|
|
}
|
|
}
|
|
}
|
|
|
|
FColor UMeshPaintingSubsystem::PickVertexColorFromTextureData(const uint8* MipData, const FVector2D& UVCoordinate, const UTexture2D* Texture, const FColor ColorMask)
|
|
{
|
|
checkf(MipData, TEXT("Invalid texture MIP data"));
|
|
FColor VertexColor = FColor::Black;
|
|
|
|
if ((UVCoordinate.X >= 0.0f) && (UVCoordinate.X < 1.0f) && (UVCoordinate.Y >= 0.0f) && (UVCoordinate.Y < 1.0f))
|
|
{
|
|
const int32 X = Texture->GetSizeX()*UVCoordinate.X;
|
|
const int32 Y = Texture->GetSizeY()*UVCoordinate.Y;
|
|
|
|
const int32 Index = ((Y * Texture->GetSizeX()) + X) * 4;
|
|
VertexColor.B = MipData[Index + 0];
|
|
VertexColor.G = MipData[Index + 1];
|
|
VertexColor.R = MipData[Index + 2];
|
|
VertexColor.A = MipData[Index + 3];
|
|
|
|
VertexColor.DWColor() &= ColorMask.DWColor();
|
|
}
|
|
|
|
// Vertex color is linear
|
|
if (Texture->SRGB)
|
|
{
|
|
VertexColor = FLinearColor(VertexColor).ToFColor(false);
|
|
}
|
|
|
|
return VertexColor;
|
|
}
|
|
|
|
|
|
bool UMeshPaintingSubsystem::GetPerVertexPaintInfluencedVertices(FPerVertexPaintActionArgs& InArgs, TSet<int32>& InfluencedVertices)
|
|
{
|
|
// Retrieve components world matrix
|
|
const FMatrix& ComponentToWorldMatrix = InArgs.Adapter->GetComponentToWorldMatrix();
|
|
|
|
// Compute the camera position in actor space. We need this later to check for back facing triangles.
|
|
const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(InArgs.CameraPosition));
|
|
const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(InArgs.HitResult.Location));
|
|
|
|
// @todo MeshPaint: Input vector doesn't work well with non-uniform scale
|
|
const float BrushRadius = InArgs.BrushProperties->BrushRadius;
|
|
const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushRadius, 0.0f, 0.0f)).Size();
|
|
const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius;
|
|
|
|
// Get a list of unique vertices indexed by the influenced triangles
|
|
InArgs.Adapter->GetInfluencedVertexIndices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, InArgs.BrushProperties->bOnlyFrontFacingTriangles, InfluencedVertices);
|
|
|
|
return (InfluencedVertices.Num() > 0);
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::ApplyPerVertexPaintAction(FPerVertexPaintActionArgs& InArgs, FPerVertexPaintAction Action)
|
|
{
|
|
// Get a list of unique vertices indexed by the influenced triangles
|
|
TSet<int32> InfluencedVertices;
|
|
GetPerVertexPaintInfluencedVertices(InArgs, InfluencedVertices);
|
|
|
|
if (InfluencedVertices.Num())
|
|
{
|
|
InArgs.Adapter->PreEdit();
|
|
for (const int32 VertexIndex : InfluencedVertices)
|
|
{
|
|
// Apply the action!
|
|
Action.ExecuteIfBound(InArgs, VertexIndex);
|
|
}
|
|
InArgs.Adapter->PostEdit();
|
|
}
|
|
|
|
return (InfluencedVertices.Num() > 0);
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::ApplyPerTrianglePaintAction(IMeshPaintComponentAdapter* Adapter, const FVector& CameraPosition, const FVector& HitPosition, const UBrushBaseProperties* Settings, FPerTrianglePaintAction Action, bool bOnlyFrontFacingTriangles)
|
|
{
|
|
// Retrieve components world matrix
|
|
const FMatrix& ComponentToWorldMatrix = Adapter->GetComponentToWorldMatrix();
|
|
|
|
// Compute the camera position in actor space. We need this later to check for back facing triangles.
|
|
const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(CameraPosition));
|
|
const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitPosition));
|
|
|
|
// @todo MeshPaint: Input vector doesn't work well with non-uniform scale
|
|
const float BrushRadius = Settings->BrushRadius;
|
|
const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushRadius, 0.0f, 0.0f)).Size();
|
|
const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius;
|
|
|
|
// Get a list of (optionally front-facing) triangles that are within a reasonable distance to the brush
|
|
TArray<uint32> InfluencedTriangles = Adapter->SphereIntersectTriangles(
|
|
ComponentSpaceSquaredBrushRadius,
|
|
ComponentSpaceBrushPosition,
|
|
ComponentSpaceCameraPosition,
|
|
bOnlyFrontFacingTriangles);
|
|
|
|
int32 TriangleIndices[3];
|
|
|
|
const TArray<uint32> VertexIndices = Adapter->GetMeshIndices();
|
|
for (uint32 TriangleIndex : InfluencedTriangles)
|
|
{
|
|
// Grab the vertex indices and points for this triangle
|
|
for (int32 TriVertexNum = 0; TriVertexNum < 3; ++TriVertexNum)
|
|
{
|
|
TriangleIndices[TriVertexNum] = VertexIndices[TriangleIndex * 3 + TriVertexNum];
|
|
}
|
|
|
|
Action.Execute(Adapter, TriangleIndex, TriangleIndices);
|
|
}
|
|
|
|
return (InfluencedTriangles.Num() > 0);
|
|
}
|
|
|
|
struct FPaintedMeshVertex
|
|
{
|
|
FVector Position;
|
|
FPackedNormal Normal;
|
|
FColor Color;
|
|
};
|
|
|
|
/** Helper struct for the mesh component vert position octree */
|
|
struct FVertexColorPropogationOctreeSemantics
|
|
{
|
|
enum { MaxElementsPerLeaf = 16 };
|
|
enum { MinInclusiveElementsPerNode = 7 };
|
|
enum { MaxNodeDepth = 12 };
|
|
|
|
typedef TInlineAllocator<MaxElementsPerLeaf> ElementAllocator;
|
|
|
|
/**
|
|
* Get the bounding box of the provided octree element. In this case, the box
|
|
* is merely the point specified by the element.
|
|
*
|
|
* @param Element Octree element to get the bounding box for
|
|
*
|
|
* @return Bounding box of the provided octree element
|
|
*/
|
|
FORCEINLINE static FBoxCenterAndExtent GetBoundingBox( const FPaintedMeshVertex& Element )
|
|
{
|
|
return FBoxCenterAndExtent( Element.Position, FVector::ZeroVector );
|
|
}
|
|
|
|
/**
|
|
* Determine if two octree elements are equal
|
|
*
|
|
* @param A First octree element to check
|
|
* @param B Second octree element to check
|
|
*
|
|
* @return true if both octree elements are equal, false if they are not
|
|
*/
|
|
FORCEINLINE static bool AreElementsEqual( const FPaintedMeshVertex& A, const FPaintedMeshVertex& B )
|
|
{
|
|
return ( A.Position == B.Position && A.Normal == B.Normal && A.Color == B.Color );
|
|
}
|
|
|
|
/** Ignored for this implementation */
|
|
FORCEINLINE static void SetElementId( const FPaintedMeshVertex& Element, FOctreeElementId2 Id )
|
|
{
|
|
}
|
|
};
|
|
typedef TOctree2<FPaintedMeshVertex, FVertexColorPropogationOctreeSemantics> TVertexColorPropogationPosOctree;
|
|
|
|
void UMeshPaintingSubsystem::ApplyVertexColorsToAllLODs(IMeshPaintComponentAdapter& GeometryInfo, USkeletalMeshComponent* SkeletalMeshComponent)
|
|
{
|
|
checkf(SkeletalMeshComponent != nullptr, TEXT("Invalid Skeletal Mesh Component"));
|
|
USkeletalMesh* Mesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
|
|
if (Mesh)
|
|
{
|
|
FSkeletalMeshRenderData* Resource = Mesh->GetResourceForRendering();
|
|
FSkeletalMeshModel* SrcMesh = Mesh->GetImportedModel();
|
|
if (Resource)
|
|
{
|
|
const int32 NumLODs = Resource->LODRenderData.Num();
|
|
if (NumLODs > 1)
|
|
{
|
|
const FSkeletalMeshLODRenderData& BaseLOD = Resource->LODRenderData[0];
|
|
GeometryInfo.PreEdit();
|
|
|
|
PropagateVertexPaintToSkeletalMesh(Mesh, 0);
|
|
|
|
FBox BaseBounds(ForceInitToZero);
|
|
|
|
TArray<FPaintedMeshVertex> PaintedVertices;
|
|
PaintedVertices.Empty(BaseLOD.GetNumVertices());
|
|
|
|
FPaintedMeshVertex PaintedVertex;
|
|
for (uint32 VertexIndex = 0; VertexIndex < BaseLOD.GetNumVertices(); ++VertexIndex )
|
|
{
|
|
const FVector VertexPos = (FVector)BaseLOD.StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex);
|
|
|
|
FPackedNormal VertexTangentX, VertexTangentZ;
|
|
VertexTangentX = BaseLOD.StaticVertexBuffers.StaticMeshVertexBuffer.VertexTangentX(VertexIndex);
|
|
VertexTangentZ = BaseLOD.StaticVertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(VertexIndex);
|
|
|
|
BaseBounds += VertexPos;
|
|
PaintedVertex.Position = VertexPos;
|
|
PaintedVertex.Normal = VertexTangentZ;
|
|
PaintedVertex.Color = BaseLOD.StaticVertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex);
|
|
PaintedVertices.Add(PaintedVertex);
|
|
}
|
|
|
|
for (int32 LODIndex = 1; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
// Do something
|
|
FSkeletalMeshLODRenderData& ApplyLOD = Resource->LODRenderData[LODIndex];
|
|
FSkeletalMeshLODModel& SrcLOD = SrcMesh->LODModels[LODIndex];
|
|
|
|
FBox CombinedBounds = BaseBounds;
|
|
Mesh->GetLODInfo(LODIndex)->bHasPerLODVertexColors = false;
|
|
|
|
if (!ApplyLOD.StaticVertexBuffers.ColorVertexBuffer.IsInitialized())
|
|
{
|
|
ApplyLOD.StaticVertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor::White, ApplyLOD.GetNumVertices());
|
|
}
|
|
|
|
for (uint32 VertIndex=0; VertIndex<ApplyLOD.GetNumVertices(); VertIndex++)
|
|
{
|
|
const FVector VertexPos = (FVector)ApplyLOD.StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertIndex);
|
|
CombinedBounds += VertexPos;
|
|
}
|
|
|
|
TVertexColorPropogationPosOctree VertPosOctree(CombinedBounds.GetCenter(), CombinedBounds.GetExtent().GetMax());
|
|
|
|
// Add each old vertex to the octree
|
|
for (const FPaintedMeshVertex& Vertex : PaintedVertices)
|
|
{
|
|
VertPosOctree.AddElement(Vertex);
|
|
}
|
|
|
|
// Iterate over each new vertex position, attempting to find the old vertex it is closest to, applying
|
|
// the color of the old vertex to the new position if possible.
|
|
const float DistanceOverNormalThreshold = KINDA_SMALL_NUMBER;
|
|
check(SrcLOD.NumVertices == ApplyLOD.GetNumVertices());
|
|
for (uint32 VertexIndex = 0; VertexIndex < ApplyLOD.GetNumVertices(); ++VertexIndex)
|
|
{
|
|
TArray<FPaintedMeshVertex> PointsToConsider;
|
|
const FVector CurPosition = (FVector)ApplyLOD.StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex);
|
|
|
|
FPackedNormal VertexTangentZ;
|
|
VertexTangentZ = BaseLOD.StaticVertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(VertexIndex);
|
|
|
|
FVector CurNormal = VertexTangentZ.ToFVector();
|
|
|
|
// Iterate through the octree attempting to find the vertices closest to the current new point
|
|
VertPosOctree.FindNearbyElements(CurPosition, [&PointsToConsider](const FPaintedMeshVertex& Vertex)
|
|
{
|
|
// Add all of the elements in the current node to the list of points to consider for closest point calculations
|
|
PointsToConsider.Add(Vertex);
|
|
});
|
|
|
|
// If any points to consider were found, iterate over each and find which one is the closest to the new point
|
|
if (PointsToConsider.Num() > 0)
|
|
{
|
|
int32 BestVertexIndex = 0;
|
|
FVector BestVertexNormal = PointsToConsider[BestVertexIndex].Normal.ToFVector();
|
|
|
|
float BestDistanceSquared = (PointsToConsider[BestVertexIndex].Position - CurPosition).SizeSquared();
|
|
float BestNormalDot = BestVertexNormal | CurNormal;
|
|
|
|
for (int32 ConsiderationIndex = 1; ConsiderationIndex < PointsToConsider.Num(); ++ConsiderationIndex)
|
|
{
|
|
FPaintedMeshVertex& CheckVertex = PointsToConsider[ConsiderationIndex];
|
|
FVector VertexNormal = CheckVertex.Normal.ToFVector();
|
|
|
|
const float DistSqrd = (CheckVertex.Position - CurPosition).SizeSquared();
|
|
const float NormalDot = VertexNormal | CurNormal;
|
|
if (DistSqrd < BestDistanceSquared - DistanceOverNormalThreshold)
|
|
{
|
|
BestVertexIndex = ConsiderationIndex;
|
|
|
|
BestDistanceSquared = DistSqrd;
|
|
BestNormalDot = NormalDot;
|
|
}
|
|
else if (DistSqrd < BestDistanceSquared + DistanceOverNormalThreshold && NormalDot > BestNormalDot)
|
|
{
|
|
BestVertexIndex = ConsiderationIndex;
|
|
BestDistanceSquared = DistSqrd;
|
|
BestNormalDot = NormalDot;
|
|
}
|
|
}
|
|
|
|
ApplyLOD.StaticVertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex) = PointsToConsider[BestVertexIndex].Color;
|
|
// Also apply to the skeletal mesh source mesh
|
|
int32 SectionIndex = INDEX_NONE;
|
|
int32 SectionVertexIndex = INDEX_NONE;
|
|
SrcLOD.GetSectionFromVertexIndex(VertexIndex, SectionIndex, SectionVertexIndex);
|
|
SrcLOD.Sections[SectionIndex].SoftVertices[SectionVertexIndex].Color = PointsToConsider[BestVertexIndex].Color;
|
|
}
|
|
}
|
|
PropagateVertexPaintToSkeletalMesh(Mesh, LODIndex);
|
|
}
|
|
|
|
GeometryInfo.PostEdit();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<IMeshPaintComponentAdapter> UMeshPaintingSubsystem::GetAdapterForComponent(const UMeshComponent* InComponent) const
|
|
{
|
|
return InComponent ? ComponentToAdapterMap.FindRef(InComponent->GetPathName()) : TSharedPtr<IMeshPaintComponentAdapter>{};
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::AddToComponentToAdapterMap(const UMeshComponent* InComponent, const TSharedPtr<IMeshPaintComponentAdapter> InAdapter)
|
|
{
|
|
if (!InAdapter || !InComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ComponentToAdapterMap.Add(InComponent->GetPathName(), InAdapter);
|
|
}
|
|
|
|
TArray<UMeshComponent*> UMeshPaintingSubsystem::GetSelectedMeshComponents() const
|
|
{
|
|
TArray<UMeshComponent*> Result;
|
|
Result.Reserve(SelectedMeshComponents.Num());
|
|
|
|
for (TWeakObjectPtr<UMeshComponent> SelectedMeshComponent : SelectedMeshComponents)
|
|
{
|
|
if (SelectedMeshComponent.IsValid())
|
|
{
|
|
Result.Add(SelectedMeshComponent.Get());
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ClearSelectedMeshComponents()
|
|
{
|
|
SelectedMeshComponents.Empty();
|
|
}
|
|
|
|
|
|
void UMeshPaintingSubsystem::AddSelectedMeshComponents(const TArray<UMeshComponent*>& InComponents)
|
|
{
|
|
SelectedMeshComponents.Reserve(SelectedMeshComponents.Num() + InComponents.Num());
|
|
Algo::Copy(InComponents, SelectedMeshComponents);
|
|
}
|
|
|
|
|
|
TArray<UMeshComponent*> UMeshPaintingSubsystem::GetPaintableMeshComponents() const
|
|
{
|
|
TArray<UMeshComponent*> Result;
|
|
Result.Reserve(PaintableComponents.Num());
|
|
|
|
for (TWeakObjectPtr<UMeshComponent> PaintableComponent : PaintableComponents)
|
|
{
|
|
if (PaintableComponent.IsValid())
|
|
{
|
|
Result.Add(PaintableComponent.Get());
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::AddPaintableMeshComponent(UMeshComponent* InComponent)
|
|
{
|
|
PaintableComponents.Add(InComponent);
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ClearPaintableMeshComponents()
|
|
{
|
|
PaintableComponents.Empty();
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::ResetState()
|
|
{
|
|
PaintableComponents.Empty();
|
|
SelectedMeshComponents.Empty();
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::Refresh()
|
|
{
|
|
// Ensure that we call OnRemoved while adapter/components are still valid
|
|
PaintableComponents.Empty();
|
|
CleanUp();
|
|
|
|
bNeedsRecache = true;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::CleanUp()
|
|
{
|
|
LastPaintedComponent = nullptr;
|
|
|
|
for (auto& MeshAdapterPair : ComponentToAdapterMap)
|
|
{
|
|
MeshAdapterPair.Value->OnRemoved();
|
|
}
|
|
ComponentToAdapterMap.Empty();
|
|
FMeshPaintComponentAdapterFactory::CleanupGlobals();
|
|
}
|
|
|
|
bool UMeshPaintingSubsystem::FindHitResult(const FRay Ray, FHitResult& BestTraceResult)
|
|
{
|
|
const FVector& Origin = Ray.Origin;
|
|
const FVector& Direction = Ray.Direction;
|
|
BestTraceResult.Distance = FLT_MAX;
|
|
// Fire out a ray to see if there is a *selected* component under the mouse cursor that can be painted.
|
|
{
|
|
const FVector TraceStart(Origin);
|
|
const FVector TraceEnd(Origin + Direction * HALF_WORLD_MAX);
|
|
|
|
for (UMeshComponent* MeshComponent : GetPaintableMeshComponents())
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = GetAdapterForComponent(MeshComponent);
|
|
if (!MeshAdapter.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ray trace
|
|
FHitResult TraceHitResult(1.0f);
|
|
|
|
if (MeshAdapter->LineTraceComponent(TraceHitResult, TraceStart, TraceEnd, FCollisionQueryParams(SCENE_QUERY_STAT(Paint), true)))
|
|
{
|
|
// Find the closest impact
|
|
if ((BestTraceResult.GetComponent() == nullptr) || (TraceHitResult.Distance < BestTraceResult.Distance))
|
|
{
|
|
BestTraceResult = TraceHitResult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return BestTraceResult.Distance != FLT_MAX;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::UpdatePaintSupportState()
|
|
{
|
|
bSelectionSupportsVertexPaint = false;
|
|
bSelectionSupportsTextureColorPaint = false;
|
|
bSelectionSupportsTextureAssetPaint = false;
|
|
|
|
TArray<FPaintableTexture> CommonPaintableTextures;
|
|
bool bCommonPaintableTexturesInitialized = false;
|
|
|
|
for (TWeakObjectPtr<UMeshComponent> MeshComponentWeak : SelectedMeshComponents)
|
|
{
|
|
if (UMeshComponent* MeshComponent = MeshComponentWeak.Get())
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = GetAdapterForComponent(MeshComponent);
|
|
if (!MeshAdapter.IsValid())
|
|
{
|
|
MeshAdapter = FMeshPaintComponentAdapterFactory::CreateAdapterForMesh(MeshComponent, 0);
|
|
if (MeshAdapter)
|
|
{
|
|
AddToComponentToAdapterMap(MeshComponent, MeshAdapter);
|
|
}
|
|
}
|
|
|
|
if (MeshAdapter)
|
|
{
|
|
bSelectionSupportsVertexPaint |= MeshAdapter->SupportsVertexPaint();
|
|
|
|
const bool bSupportsTextureColorPaint = MeshAdapter->SupportsTextureColorPaint();
|
|
const bool bSupportsTextureAssetPaint = MeshAdapter->SupportsTexturePaint();
|
|
|
|
if (bSupportsTextureColorPaint || bSupportsTextureAssetPaint)
|
|
{
|
|
// Collect PaintableTextures. This if for both TextureColor painting (MeshPaintTextures on components) and TextureAsset painting (Textures ref'd in materials).
|
|
int32 DummyDefaultIndex = INDEX_NONE;
|
|
TArray<FPaintableTexture> PaintableTextures;
|
|
UTexturePaintToolset::RetrieveTexturesForComponent(MeshComponent, MeshAdapter.Get(), DummyDefaultIndex, PaintableTextures);
|
|
|
|
for (FPaintableTexture const& PaintableTexture : PaintableTextures)
|
|
{
|
|
// The bIsMeshTexture tells us which mode the paintable texture works with.
|
|
bSelectionSupportsTextureColorPaint |= bSupportsTextureColorPaint && PaintableTexture.bIsMeshTexture;
|
|
bSelectionSupportsTextureAssetPaint |= bSupportsTextureAssetPaint && !PaintableTexture.bIsMeshTexture;
|
|
}
|
|
|
|
if (bSupportsTextureAssetPaint)
|
|
{
|
|
// Fill CommonPaintableTextures array with textures that are in present in ALL components.
|
|
if (!bCommonPaintableTexturesInitialized)
|
|
{
|
|
CommonPaintableTextures = MoveTemp(PaintableTextures);
|
|
bCommonPaintableTexturesInitialized = true;
|
|
}
|
|
else
|
|
{
|
|
TArray<FPaintableTexture> NewCommonPaintableTextures;
|
|
for (FPaintableTexture const& PaintableTexture : PaintableTextures)
|
|
{
|
|
if (CommonPaintableTextures.Contains(PaintableTexture))
|
|
{
|
|
NewCommonPaintableTextures.Add(PaintableTexture);
|
|
}
|
|
}
|
|
CommonPaintableTextures = MoveTemp(NewCommonPaintableTextures);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Texture Asset painting requires some texture that is common to all selected components.
|
|
CommonPaintableTextures.RemoveAll([](FPaintableTexture const& PaintableTexture) { return PaintableTexture.bIsMeshTexture; });
|
|
bSelectionSupportsTextureAssetPaint &= CommonPaintableTextures.Num() > 0;
|
|
}
|
|
|
|
TArray<FPerComponentVertexColorData> UMeshPaintingSubsystem::GetCopiedColorsByComponent() const
|
|
{
|
|
return CopiedColorsByComponent;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::SetCopiedColorsByComponent(TArray<FPerComponentVertexColorData>& InCopiedColors)
|
|
{
|
|
CopiedColorsByComponent = InCopiedColors;
|
|
}
|
|
|
|
FImage const& UMeshPaintingSubsystem::GetCopiedTexture() const
|
|
{
|
|
return CopiedTextureData;
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::SetCopiedTexture(UTexture* InTexture)
|
|
{
|
|
if (InTexture != nullptr)
|
|
{
|
|
InTexture->Source.GetMipImage(CopiedTextureData, 0);
|
|
}
|
|
}
|
|
|
|
void UMeshPaintingSubsystem::CacheSelectionData(const int32 PaintLODIndex, const int32 UVChannel)
|
|
{
|
|
bSelectionContainsPerLODColors = false;
|
|
for (UMeshComponent* MeshComponent : GetSelectedMeshComponents())
|
|
{
|
|
// Don't create an adapter for transient components, as it doesn't make sense to edit them if they will not be saved.
|
|
// (An example is the procedurally generated foliage meshes)
|
|
if (!MeshComponent->HasAnyFlags(RF_Transient))
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = FMeshPaintComponentAdapterFactory::CreateAdapterForMesh(MeshComponent, PaintLODIndex);
|
|
if (MeshComponent->IsVisible() && MeshAdapter.IsValid() && MeshAdapter->IsValid())
|
|
{
|
|
TUniquePtr< FComponentReregisterContext > ComponentReregisterContext;
|
|
AddPaintableMeshComponent(MeshComponent);
|
|
AddToComponentToAdapterMap(MeshComponent, MeshAdapter);
|
|
MeshAdapter->OnAdded();
|
|
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->ForceRenderMeshLOD(MeshComponent, PaintLODIndex);
|
|
ComponentReregisterContext.Reset(new FComponentReregisterContext(MeshComponent));
|
|
bSelectionContainsPerLODColors |= GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->DoesMeshComponentContainPerLODColors(MeshComponent);
|
|
}
|
|
}
|
|
}
|
|
bNeedsRecache = false;
|
|
}
|
|
|
|
FIntPoint UMeshPaintingSubsystem::GetMinMaxUVChannelsToPaint() const
|
|
{
|
|
// For multiple selection take the value from the component with fewest UV channels.
|
|
int32 MinNumUVs = INDEX_NONE;
|
|
int32 MaxNumUVs = INDEX_NONE;
|
|
for (TWeakObjectPtr<UMeshComponent> PaintableComponent : PaintableComponents)
|
|
{
|
|
if (UMeshComponent* MeshComponent = PaintableComponent.Get())
|
|
{
|
|
const int32 NumUVs = GetNumberOfUVs(MeshComponent, 0);
|
|
MinNumUVs = MinNumUVs == INDEX_NONE ? NumUVs : FMath::Min(MinNumUVs, NumUVs);
|
|
MaxNumUVs = MaxNumUVs == INDEX_NONE ? NumUVs : FMath::Max(MaxNumUVs, NumUVs);
|
|
}
|
|
}
|
|
|
|
MinNumUVs = MinNumUVs == INDEX_NONE ? 0 : MinNumUVs;
|
|
MaxNumUVs = MaxNumUVs == INDEX_NONE ? 0 : MaxNumUVs;
|
|
return FIntPoint(MinNumUVs, MaxNumUVs);
|
|
}
|
|
|
|
TCHAR const* FMeshPaintToolSettingHelpers::GetCacheIdentifier()
|
|
{
|
|
return TEXT("MeshPaint");
|
|
}
|
|
|
|
void FMeshPaintToolSettingHelpers::SavePropertiesForClassHeirachy(UInteractiveTool* InTool, UInteractiveToolPropertySet* InProperties)
|
|
{
|
|
// Properties are saved in a cache on the class CDO.
|
|
// So we need to iterate the class structure and save for each class in the hierachy.
|
|
// That way classes that share a base class will both use the same cache for their shared properties.
|
|
// Specifically we keep the same base class brush settings when switching between the different painting tools.
|
|
const FString CacheIdentifier(GetCacheIdentifier());
|
|
InProperties->SaveProperties(InTool, CacheIdentifier);
|
|
|
|
UEngine::FCopyPropertiesForUnrelatedObjectsParams Params;
|
|
Params.bOnlyHandleDirectSubObjects = true;
|
|
|
|
UClass* Class = InProperties->GetClass();
|
|
if (Class != UInteractiveToolPropertySet::StaticClass())
|
|
{
|
|
Class = Class->GetSuperClass();
|
|
while (Class != UInteractiveToolPropertySet::StaticClass())
|
|
{
|
|
UInteractiveToolPropertySet* Properties = NewObject<UInteractiveToolPropertySet>(GetTransientPackage(), Class);
|
|
UEngine::CopyPropertiesForUnrelatedObjects(InProperties, Properties, Params);
|
|
Properties->SaveProperties(InTool, CacheIdentifier);
|
|
Class = Class->GetSuperClass();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshPaintToolSettingHelpers::RestorePropertiesForClassHeirachy(UInteractiveTool* InTool, UInteractiveToolPropertySet* InProperties)
|
|
{
|
|
// Restore from leaf class down the hierachy, with each base class overriding what was written before.
|
|
const FString CacheIdentifier(GetCacheIdentifier());
|
|
InProperties->RestoreProperties(InTool, CacheIdentifier);
|
|
|
|
UEngine::FCopyPropertiesForUnrelatedObjectsParams Params;
|
|
Params.bOnlyHandleDirectSubObjects = true;
|
|
|
|
UClass* Class = InProperties->GetClass();
|
|
if (Class != UInteractiveToolPropertySet::StaticClass())
|
|
{
|
|
Class = Class->GetSuperClass();
|
|
while (Class != UInteractiveToolPropertySet::StaticClass())
|
|
{
|
|
UInteractiveToolPropertySet* Properties = NewObject<UInteractiveToolPropertySet>(GetTransientPackage(), Class);
|
|
Properties->RestoreProperties(InTool, CacheIdentifier);
|
|
UEngine::CopyPropertiesForUnrelatedObjects(Properties, InProperties, Params);
|
|
Class = Class->GetSuperClass();
|
|
}
|
|
}
|
|
}
|