309 lines
10 KiB
C++
309 lines
10 KiB
C++
// Copyright 2020 Phyronnaz
|
|
|
|
#include "VoxelTools/VoxelPaintMaterial.h"
|
|
#include "VoxelUtilities/VoxelMathUtilities.h"
|
|
|
|
void FVoxelPaintMaterial::ApplyToMaterial(FVoxelMaterial& Material, float Strength) const
|
|
{
|
|
// Else goes way beyond target
|
|
Strength = FMath::Clamp(Strength, -1.f, 1.f);
|
|
|
|
switch (Type)
|
|
{
|
|
case EVoxelPaintMaterialType::Color:
|
|
{
|
|
// Note: the voxel colors are in linear space, even if they are stored in FColors
|
|
FColor ColorToPaint = Color.bUseLinearColor ? Color.LinearColor.ToFColor(false) : Color.Color;
|
|
if (Strength < 0)
|
|
{
|
|
Strength = -Strength;
|
|
ColorToPaint = FColor(0, 0, 0, 0);
|
|
}
|
|
if (Color.bPaintR)
|
|
{
|
|
Material.SetR(FVoxelUtilities::LerpUINT8(Material.GetR(), ColorToPaint.R, Strength));
|
|
}
|
|
if (Color.bPaintG)
|
|
{
|
|
Material.SetG(FVoxelUtilities::LerpUINT8(Material.GetG(), ColorToPaint.G, Strength));
|
|
}
|
|
if (Color.bPaintB)
|
|
{
|
|
Material.SetB(FVoxelUtilities::LerpUINT8(Material.GetB(), ColorToPaint.B, Strength));
|
|
}
|
|
if (Color.bPaintA)
|
|
{
|
|
Material.SetA(FVoxelUtilities::LerpUINT8(Material.GetA(), ColorToPaint.A, Strength));
|
|
}
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::FiveWayBlend:
|
|
{
|
|
float TargetValue = Strength > 0 ? FiveWayBlend.TargetValue : 1.f - FiveWayBlend.TargetValue;
|
|
Strength = FMath::Abs(Strength);
|
|
|
|
if (FiveWayBlend.bFourWayBlend)
|
|
{
|
|
const int32 Channel = FMath::Clamp(FiveWayBlend.Channel, 0, 3);
|
|
|
|
const float R = Material.GetR_AsFloat();
|
|
const float G = Material.GetG_AsFloat();
|
|
const float B = Material.GetB_AsFloat();
|
|
|
|
TVoxelStaticArray<float, 4> Strengths = FVoxelUtilities::XWayBlend_AlphasToStrengths_Static<4>({ R, G, B });
|
|
|
|
// Add locked channels to ChannelsToKeepIntact,
|
|
// and make TargetValue relative to the strength that's left to edit
|
|
uint32 ChannelsToKeepIntact = 1u << Channel;
|
|
{
|
|
float LockedChannelsStrength = 0.f;
|
|
for (uint8 LockedChannel : FiveWayBlend.LockedChannels)
|
|
{
|
|
LockedChannel = FMath::Clamp<uint8>(LockedChannel, 0, 3);
|
|
ChannelsToKeepIntact |= 1u << LockedChannel;
|
|
LockedChannelsStrength += Strengths[LockedChannel];
|
|
}
|
|
TargetValue *= 1.f - LockedChannelsStrength;
|
|
}
|
|
|
|
Strengths[Channel] = FMath::Clamp(FMath::Lerp(Strengths[Channel], TargetValue, Strength), 0.f, 1.f);
|
|
|
|
const TVoxelStaticArray<float, 3> Alphas = FVoxelUtilities::XWayBlend_StrengthsToAlphas_Static<4>(Strengths, ChannelsToKeepIntact);
|
|
|
|
Material.SetR(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[0], R));
|
|
Material.SetG(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[1], G));
|
|
Material.SetB(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[2], B));
|
|
}
|
|
else
|
|
{
|
|
const int32 Channel = FMath::Clamp(FiveWayBlend.Channel, 0, 4);
|
|
|
|
const float R = Material.GetR_AsFloat();
|
|
const float G = Material.GetG_AsFloat();
|
|
const float B = Material.GetB_AsFloat();
|
|
const float A = Material.GetA_AsFloat();
|
|
|
|
TVoxelStaticArray<float, 5> Strengths = FVoxelUtilities::XWayBlend_AlphasToStrengths_Static<5>({ R, G, B, A });
|
|
|
|
// Add locked channels to ChannelsToKeepIntact,
|
|
// and make TargetValue relative to the strength that's left to edit
|
|
uint32 ChannelsToKeepIntact = 1u << Channel;
|
|
{
|
|
float LockedChannelsStrength = 0.f;
|
|
for (uint8 LockedChannel : FiveWayBlend.LockedChannels)
|
|
{
|
|
LockedChannel = FMath::Clamp<uint8>(LockedChannel, 0, 4);
|
|
ChannelsToKeepIntact |= 1u << LockedChannel;
|
|
LockedChannelsStrength += Strengths[LockedChannel];
|
|
}
|
|
TargetValue *= 1.f - LockedChannelsStrength;
|
|
}
|
|
|
|
Strengths[Channel] = FMath::Clamp(FMath::Lerp(Strengths[Channel], TargetValue, Strength), 0.f, 1.f);
|
|
|
|
const TVoxelStaticArray<float, 4> Alphas = FVoxelUtilities::XWayBlend_StrengthsToAlphas_Static<5>(Strengths, ChannelsToKeepIntact);
|
|
|
|
Material.SetR(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[0], R));
|
|
Material.SetG(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[1], G));
|
|
Material.SetB(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[2], B));
|
|
Material.SetA(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[3], A));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::SingleIndex:
|
|
{
|
|
Material.SetSingleIndex(SingleIndex.Channel.Channel);
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::MultiIndex:
|
|
{
|
|
float TargetValue = Strength > 0 ? MultiIndex.TargetValue : 1.f - MultiIndex.TargetValue;
|
|
Strength = FMath::Abs(Strength);
|
|
|
|
const float Blend0 = Material.GetMultiIndex_Blend0_AsFloat();
|
|
const float Blend1 = Material.GetMultiIndex_Blend1_AsFloat();
|
|
const float Blend2 = Material.GetMultiIndex_Blend2_AsFloat();
|
|
|
|
TVoxelStaticArray<float, 4> Strengths = FVoxelUtilities::XWayBlend_AlphasToStrengths_Static<4>({ Blend0, Blend1, Blend2 });
|
|
|
|
uint32 ChannelsToKeepIntact = 0;
|
|
|
|
// Propagate strengths backward if the indices are equal, and mark them as fixed:
|
|
// we want them to always be 0, and the "main" one have all the strength instead
|
|
// This is to handle cases where indices are eg 1 0 0 0
|
|
{
|
|
if (Material.GetMultiIndex_Index3() == Material.GetMultiIndex_Index2())
|
|
{
|
|
Strengths[2] += Strengths[3];
|
|
Strengths[3] = 0;
|
|
ChannelsToKeepIntact |= 1u << 3;
|
|
}
|
|
else if (Material.GetMultiIndex_Index3() == Material.GetMultiIndex_Index1())
|
|
{
|
|
Strengths[1] += Strengths[3];
|
|
Strengths[3] = 0;
|
|
ChannelsToKeepIntact |= 1u << 3;
|
|
}
|
|
else if (Material.GetMultiIndex_Index3() == Material.GetMultiIndex_Index0())
|
|
{
|
|
Strengths[0] += Strengths[3];
|
|
Strengths[3] = 0;
|
|
ChannelsToKeepIntact |= 1u << 3;
|
|
}
|
|
|
|
if (Material.GetMultiIndex_Index2() == Material.GetMultiIndex_Index1())
|
|
{
|
|
Strengths[1] += Strengths[2];
|
|
Strengths[2] = 0;
|
|
ChannelsToKeepIntact |= 1u << 2;
|
|
}
|
|
else if (Material.GetMultiIndex_Index2() == Material.GetMultiIndex_Index0())
|
|
{
|
|
Strengths[0] += Strengths[2];
|
|
Strengths[2] = 0;
|
|
ChannelsToKeepIntact |= 1u << 2;
|
|
}
|
|
|
|
if (Material.GetMultiIndex_Index1() == Material.GetMultiIndex_Index0())
|
|
{
|
|
Strengths[0] += Strengths[1];
|
|
Strengths[1] = 0;
|
|
ChannelsToKeepIntact |= 1u << 1;
|
|
}
|
|
}
|
|
|
|
// Add locked channels to ChannelsToKeepIntact,
|
|
// and make TargetValue relative to the strength that's left to edit
|
|
{
|
|
float LockedChannelsStrength = 0.f;
|
|
for (uint8 LockedChannel : MultiIndex.LockedChannels)
|
|
{
|
|
if (Material.GetMultiIndex_Index0() == LockedChannel)
|
|
{
|
|
ChannelsToKeepIntact |= 1u << 0;
|
|
LockedChannelsStrength += Strengths[0];
|
|
}
|
|
else if (Material.GetMultiIndex_Index1() == LockedChannel)
|
|
{
|
|
ChannelsToKeepIntact |= 1u << 1;
|
|
LockedChannelsStrength += Strengths[1];
|
|
}
|
|
else if (Material.GetMultiIndex_Index2() == LockedChannel)
|
|
{
|
|
ChannelsToKeepIntact |= 1u << 2;
|
|
LockedChannelsStrength += Strengths[2];
|
|
}
|
|
else if (Material.GetMultiIndex_Index3() == LockedChannel)
|
|
{
|
|
ChannelsToKeepIntact |= 1u << 3;
|
|
LockedChannelsStrength += Strengths[3];
|
|
}
|
|
}
|
|
TargetValue *= 1.f - LockedChannelsStrength;
|
|
}
|
|
|
|
int32 ChannelIndex;
|
|
if (MultiIndex.Channel == Material.GetMultiIndex_Index0())
|
|
{
|
|
ChannelIndex = 0;
|
|
}
|
|
else if (MultiIndex.Channel == Material.GetMultiIndex_Index1())
|
|
{
|
|
ChannelIndex = 1;
|
|
}
|
|
else if (MultiIndex.Channel == Material.GetMultiIndex_Index2())
|
|
{
|
|
ChannelIndex = 2;
|
|
}
|
|
else if (MultiIndex.Channel == Material.GetMultiIndex_Index3())
|
|
{
|
|
ChannelIndex = 3;
|
|
}
|
|
else
|
|
{
|
|
ChannelIndex = 0;
|
|
{
|
|
float MinStrength = Strengths[0];
|
|
if (Strengths[1] < MinStrength) { MinStrength = Strengths[1]; ChannelIndex = 1; }
|
|
if (Strengths[2] < MinStrength) { MinStrength = Strengths[2]; ChannelIndex = 2; }
|
|
if (Strengths[3] < MinStrength) { ChannelIndex = 3; }
|
|
}
|
|
|
|
Strengths[ChannelIndex] = 0.f;
|
|
|
|
switch (ChannelIndex)
|
|
{
|
|
case 0: Material.SetMultiIndex_Index0(MultiIndex.Channel.Channel); break;
|
|
case 1: Material.SetMultiIndex_Index1(MultiIndex.Channel.Channel); break;
|
|
case 2: Material.SetMultiIndex_Index2(MultiIndex.Channel.Channel); break;
|
|
case 3: Material.SetMultiIndex_Index3(MultiIndex.Channel.Channel); break;
|
|
default: ensureVoxelSlow(false);
|
|
}
|
|
}
|
|
|
|
// Do not modify the channel we are setting
|
|
ChannelsToKeepIntact |= 1u << ChannelIndex;
|
|
|
|
Strengths[ChannelIndex] = FMath::Clamp(FMath::Lerp(Strengths[ChannelIndex], TargetValue, Strength), 0.f, 1.f);
|
|
|
|
const auto Alphas = FVoxelUtilities::XWayBlend_StrengthsToAlphas_Static<4>(Strengths, ChannelsToKeepIntact);
|
|
|
|
Material.SetMultiIndex_Blend0(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[0], Blend0));
|
|
Material.SetMultiIndex_Blend1(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[1], Blend1));
|
|
Material.SetMultiIndex_Blend2(FVoxelUtilities::FloatToUINT8_ForLerp(Alphas[2], Blend2));
|
|
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::MultiIndexWetness:
|
|
{
|
|
float TargetValue = MultiIndexWetness.TargetValue;
|
|
if (Strength < 0)
|
|
{
|
|
Strength = -Strength;
|
|
TargetValue = 1.f - TargetValue;
|
|
}
|
|
Material.SetMultiIndex_Wetness(FVoxelUtilities::LerpUINT8(Material.GetMultiIndex_Wetness(), FVoxelUtilities::FloatToUINT8(TargetValue), Strength));
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::MultiIndexRaw:
|
|
{
|
|
TVoxelStaticArray<float, 4> Strengths;
|
|
Strengths[0] = MultiIndexRaw.Strength0;
|
|
Strengths[1] = MultiIndexRaw.Strength1;
|
|
Strengths[2] = MultiIndexRaw.Strength2;
|
|
Strengths[3] = MultiIndexRaw.Strength3;
|
|
const auto Alphas = FVoxelUtilities::XWayBlend_StrengthsToAlphas_Static<4>(Strengths);
|
|
|
|
Material.SetMultiIndex_Blend0_AsFloat(Alphas[0]);
|
|
Material.SetMultiIndex_Blend1_AsFloat(Alphas[1]);
|
|
Material.SetMultiIndex_Blend2_AsFloat(Alphas[2]);
|
|
|
|
Material.SetMultiIndex_Index0(MultiIndexRaw.Channel0.Channel);
|
|
Material.SetMultiIndex_Index1(MultiIndexRaw.Channel1.Channel);
|
|
Material.SetMultiIndex_Index2(MultiIndexRaw.Channel2.Channel);
|
|
Material.SetMultiIndex_Index3(MultiIndexRaw.Channel3.Channel);
|
|
|
|
break;
|
|
}
|
|
case EVoxelPaintMaterialType::UV:
|
|
{
|
|
FVector2D TargetUV = UV.UV;
|
|
if (Strength < 0)
|
|
{
|
|
Strength = -Strength;
|
|
TargetUV = FVector2D(1, 1) - TargetUV;
|
|
}
|
|
if (UV.bPaintU)
|
|
{
|
|
Material.SetU(UV.Channel, FVoxelUtilities::LerpUINT8(Material.GetU(UV.Channel), FVoxelUtilities::FloatToUINT8(TargetUV.X), Strength));
|
|
}
|
|
if (UV.bPaintV)
|
|
{
|
|
Material.SetV(UV.Channel, FVoxelUtilities::LerpUINT8(Material.GetV(UV.Channel), FVoxelUtilities::FloatToUINT8(TargetUV.Y), Strength));
|
|
}
|
|
break;
|
|
}
|
|
default: ensure(false);
|
|
}
|
|
}
|