Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/MeshModelingTools/Private/DisplaceMeshTool.cpp
2025-05-18 13:04:45 +08:00

1574 lines
56 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DisplaceMeshTool.h"
#include "AssetUtils/Texture2DUtil.h"
#include "Curves/CurveFloat.h"
#include "Curves/RichCurve.h"
#include "Engine/World.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "Components/DynamicMeshComponent.h"
#include "DynamicMesh/MeshNormals.h"
#include "ModelingOperators.h"
#include "Async/ParallelFor.h"
#include "ProfilingDebugging/ScopedTimers.h"
#include "MeshDescription.h"
#include "ModelingToolTargetUtil.h"
#include "Operations/PNTriangles.h"
#include "Operations/UniformTessellate.h"
#include "Operations/SelectiveTessellate.h"
#include "Materials/MaterialInterface.h"
#include "Properties/MeshStatisticsProperties.h"
#include "TargetInterfaces/MeshDescriptionProvider.h"
#include "TargetInterfaces/MeshDescriptionCommitter.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
// needed to disable normals recalculation on the underlying asset
#include "AssetUtils/MeshDescriptionUtil.h"
#include "Components/StaticMeshComponent.h"
#include "DynamicSubmesh3.h"
#include "Selections/GeometrySelection.h"
#include "Selections/GeometrySelectionUtil.h"
#include "Engine/StaticMesh.h"
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UDisplaceMeshTool"
namespace DisplaceMeshToolLocals{
namespace ComputeDisplacement
{
/// Directional Filter: Scale displacement for a given vertex based on how well
/// the vertex normal agrees with the specified direction.
struct FDirectionalFilter
{
bool bEnableFilter = false;
FVector3d FilterDirection = {1,0,0};
double FilterWidth = 0.1;
const double RampSlope = 5.0;
double FilterValue(const FVector3d& EvalNormal) const
{
if (!bEnableFilter) { return 1.0;}
double DotWithFilterDirection = EvalNormal.Dot(FilterDirection);
double Offset = 1.0 / RampSlope;
double MinX = 1.0 - (2.0 + Offset) * FilterWidth; // Start increasing here
double MaxX = FMathd::Min(1.0, MinX + Offset); // Stop increasing here
if (FMathd::Abs(MaxX - MinX) < FMathd::ZeroTolerance) { return 0.0; }
double Y = (DotWithFilterDirection - MinX) / (MaxX - MinX); // Clamped linear interpolation for the ramp region
return FMathd::Clamp(Y, 0.0, 1.0);
}
};
template<typename DisplaceFunc>
void ParallelDisplace(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TArray<FVector3d>& DisplacedPositions,
TArray<int>* VerticesToDisplace,
DisplaceFunc Displace,
bool bUseParallel = true)
{
ensure(Positions.Num() == Normals.GetNormals().Num());
ensure(Positions.Num() == DisplacedPositions.Num());
ensure(Mesh.VertexCount() == Positions.Num());
if (VerticesToDisplace)
{
// Copy over the original positions first since most likely some of the vertices won't be displaced
for (int VID : Mesh.VertexIndicesItr())
{
DisplacedPositions[VID] = Positions[VID];
}
ParallelFor(VerticesToDisplace->Num(), [&](int32 Idx)
{
const int VID = (*VerticesToDisplace)[Idx];
if (ensure(Mesh.IsVertex(VID))) // if this test fails then VerticesToDisplace is invalid
{
DisplacedPositions[VID] = Displace(VID, Positions[VID], Normals[VID]);
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
else
{
// Displace all vertices
ParallelFor(Mesh.MaxVertexID(), [&](int32 VID)
{
if (Mesh.IsVertex(VID))
{
DisplacedPositions[VID] = Displace(VID, Positions[VID], Normals[VID]);
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
}
void Constant(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TFunctionRef<float(int32, const FVector3d&, const FVector3d&)> IntensityFunc,
TArray<FVector3d>& DisplacedPositions,
TArray<int>* VerticesToDisplace = nullptr)
{
ParallelDisplace(Mesh, Positions, Normals, DisplacedPositions, VerticesToDisplace,
[&](int32 vid, const FVector3d& Position, const FVector3d& Normal)
{
double Intensity = IntensityFunc(vid, Position, Normal);
return Position + (Intensity * Normal);
});
}
void RandomNoise(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TFunctionRef<float(int32, const FVector3d&, const FVector3d&)> IntensityFunc,
int RandomSeed,
TArray<FVector3d>& DisplacedPositions,
TArray<int>* VerticesToDisplace = nullptr)
{
FMath::SRandInit(RandomSeed);
ParallelDisplace(Mesh, Positions, Normals, DisplacedPositions, VerticesToDisplace,
[&](int32 vid, const FVector3d& Position, const FVector3d& Normal)
{
// FMath::SRand() is not thread safe, hence we pass bUseParallel = false to ParallelDisplace to force
// displacement to be single threaded
double RandVal = 2.0 * (FMath::SRand() - 0.5);
double Intensity = IntensityFunc(vid, Position, Normal);
return Position + (Normal * RandVal * Intensity);
}, false);
}
void PerlinNoise(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TFunctionRef<float(int32, const FVector3d&, const FVector3d&)> IntensityFunc,
const TArray<FPerlinLayerProperties>& PerlinLayerProperties,
int RandomSeed,
TArray<FVector3d>& DisplacedPositions,
TArray<int>* VerticesToDisplace = nullptr)
{
FMath::SRandInit(RandomSeed);
const float RandomOffset = 10000.0f * FMath::SRand();
ParallelDisplace(Mesh, Positions, Normals, DisplacedPositions, VerticesToDisplace,
[&](int32 vid, const FVector3d& Position, const FVector3d& Normal)
{
// Compute the sum of Perlin noise evaluations for this point
FVector EvalLocation(Position + RandomOffset);
double TotalNoiseValue = 0.0;
for (int32 Layer = 0; Layer < PerlinLayerProperties.Num(); ++Layer)
{
TotalNoiseValue += PerlinLayerProperties[Layer].Intensity * FMath::PerlinNoise3D(PerlinLayerProperties[Layer].Frequency * EvalLocation);
}
double Intensity = IntensityFunc(vid, Position, Normal);
return Position + (TotalNoiseValue * Intensity * Normal);
});
}
void Map(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TFunctionRef<float(int32, const FVector3d&, const FVector3d&)> IntensityFunc,
const FSampledScalarField2f& DisplaceField,
TArray<FVector3d>& DisplacedPositions,
float DisplaceFieldBaseValue = 128.0/255, // value that corresponds to zero displacement
FVector2f UVScale = FVector2f(1, 1),
FVector2f UVOffset = FVector2f(0,0),
TArray<int>* VerticesToDisplace = nullptr,
FRichCurve* AdjustmentCurve = nullptr,
bool bUseParallel = true)
{
const FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes()->GetUVLayer(0);
// We set things up such that DisplaceField goes from 0 to 1 in the U direction,
// but the V direction may be shorter or longer if the texture is not square
// (it will be 1/AspectRatio)
float VHeight = DisplaceField.Height() * DisplaceField.CellDimensions.Y;
// Stores average offset for all elements sharing a vertex
TArray<double> OffsetArray;
OffsetArray.SetNumZeroed(Mesh.MaxVertexID());
auto ComputeOffsetForElement = [&](int32 ElementID)
{
FVector2f UV = UVOverlay->GetElement(ElementID);
// Adjust UV value and tile it.
// Note that we're effectively stretching the texture to be square before tiling, since this
// seems to be what non square textures do by default in UE. If we decide to tile without
// stretching by default someday, we'd do UV - FVector2f(FMath::Floor(UV.X), FMath:Floor(UV.Y/VHeight)*VHeight)
// without multiplying by VHeight afterward.
UV = UV * UVScale + UVOffset;
UV = UV - FVector2f(FMath::Floor(UV.X), FMath::Floor(UV.Y));
UV.Y *= VHeight;
double Offset = DisplaceField.BilinearSampleClamped(UV);
if (AdjustmentCurve)
{
Offset = AdjustmentCurve->Eval(Offset);
}
Offset -= DisplaceFieldBaseValue;
return Offset;
};
// Copy over the original positions if we are only displacing some of them
if (VerticesToDisplace)
{
for (int VID : Mesh.VertexIndicesItr())
{
DisplacedPositions[VID] = Positions[VID];
}
}
const int NumVertices = VerticesToDisplace ? VerticesToDisplace->Num() : Mesh.MaxVertexID();
ParallelFor(NumVertices, [&](int32 Idx)
{
const int VID = VerticesToDisplace ? (*VerticesToDisplace)[Idx] : Idx;
if (Mesh.IsVertex(VID) == false)
{
return;
}
if (UVOverlay->IsSeamVertex(VID))
{
// If the vertex is on a UV seam edge then average out offsets for all elements sharing the vertex
TArray<int> TrianglesOut;
Mesh.GetVtxTriangles(VID, TrianglesOut);
for (const int TID : TrianglesOut)
{
int ElementID = UVOverlay->GetElementIDAtVertex(TID, VID);
double Offset = ComputeOffsetForElement(ElementID);
OffsetArray[VID] += Offset;
}
checkSlow(TrianglesOut.Num() > 0);
OffsetArray[VID] /= TrianglesOut.Num();
}
else
{
// Grab any triangle if it's not a uv seam vertex
int TID = Mesh.GetVtxSingleTriangle(VID);
int ElementID = UVOverlay->GetElementIDAtVertex(TID, VID);
double Offset = ComputeOffsetForElement(ElementID);
OffsetArray[VID] = Offset;
}
double Intensity = IntensityFunc(VID, Positions[VID], Normals[VID]);
DisplacedPositions[VID] = Positions[VID] + (OffsetArray[VID] * Intensity * Normals[VID]);
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
}
void Sine(const FDynamicMesh3& Mesh,
const TArray<FVector3d>& Positions,
const FMeshNormals& Normals,
TFunctionRef<float(int32, const FVector3d&, const FVector3d&)> IntensityFunc,
double Frequency,
double PhaseShift,
const FVector3d& Direction,
TArray<FVector3d>& DisplacedPositions,
TArray<int>* VerticesToDisplace = nullptr)
{
FQuaterniond RotateToDirection(Direction, { 0.0, 0.0, 1.0 });
ParallelDisplace(Mesh, Positions, Normals, DisplacedPositions, VerticesToDisplace,
[&](int32 vid, const FVector3d& Position, const FVector3d& Normal)
{
FVector3d RotatedPosition = RotateToDirection * Position;
double DistXY = FMath::Sqrt(RotatedPosition.X * RotatedPosition.X + RotatedPosition.Y * RotatedPosition.Y);
double Intensity = IntensityFunc(vid, Position, Normal);
FVector3d Offset = Intensity * FMath::Sin(Frequency * DistXY + PhaseShift) * Direction;
return Position + Offset;
});
}
}
/** A collection of subdivision parameters. */
struct FSubdivideParameters
{
int SubdivisionsCount = -1;
TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMap = nullptr;
TArray<int> SelectedTriangles;
// Optional selection parameters
TOptional<EDisplaceMeshToolTriangleSelectionType> SelectionType;
// Material selection variables (SelectionType == EDisplaceMeshToolTriangleSelectionType::Material)
TOptional<int> ActiveMaterialID;
};
class FSubdivideMeshOp : public FDynamicMeshOperator
{
public:
FSubdivideMeshOp(const FDynamicMesh3& SourceMesh, const FSubdivideParameters& InParameters, EDisplaceMeshToolSubdivisionType InSubdivisionType);
void CalculateResult(FProgressCancel* Progress) final;
TUniquePtr<TArray<int>> ExtractVerticesToDisplace()
{
return MoveTemp(VerticesToDisplace);
}
protected:
TUniquePtr<TArray<int>> VerticesToDisplace = nullptr;
private:
FSubdivideParameters Parameters;
EDisplaceMeshToolSubdivisionType SubdivisionType;
};
FSubdivideMeshOp::FSubdivideMeshOp(const FDynamicMesh3& SourceMesh, const FSubdivideParameters& InParameters, EDisplaceMeshToolSubdivisionType InSubdivisionType)
:
Parameters(InParameters), SubdivisionType(InSubdivisionType)
{
ResultMesh->Copy(SourceMesh);
// If we have a WeightMap, initialize VertexUV.X with weightmap value. Note that we are going to process .Y anyway,
// we could (for exmaple) speculatively compute another weightmap, or store previous weightmap values there, to support
// fast switching between two...
ResultMesh->EnableVertexUVs(FVector2f::Zero());
if (Parameters.WeightMap != nullptr)
{
for (int32 vid : ResultMesh->VertexIndicesItr())
{
ResultMesh->SetVertexUV(vid, FVector2f(Parameters.WeightMap->GetValue(vid), 0));
}
}
else
{
for (int32 vid : ResultMesh->VertexIndicesItr())
{
ResultMesh->SetVertexUV(vid, FVector2f::One());
}
}
}
void FSubdivideMeshOp::CalculateResult(FProgressCancel* ProgressCancel)
{
const int SubdivisionsCount = Parameters.SubdivisionsCount;
bool bUsingSelection = Parameters.SelectedTriangles.Num() != ResultMesh->TriangleCount();
if (SubdivisionType == EDisplaceMeshToolSubdivisionType::Flat)
{
if (Parameters.SelectionType.IsSet()) // user wants to only tessellate a subset of the triangles
{
EDisplaceMeshToolTriangleSelectionType SelectionType = Parameters.SelectionType.GetValue();
if (SelectionType == EDisplaceMeshToolTriangleSelectionType::Material &&
Parameters.ActiveMaterialID.IsSet() &&
Parameters.ActiveMaterialID.GetValue() != INDEX_NONE)
{
TUniquePtr<FTessellationPattern> Pattern;
if (bUsingSelection)
{
Pattern = FSelectiveTessellate::CreateConcentricRingsPatternFromSelectionAndMaterial(ResultMesh.Get(), SubdivisionsCount, Parameters.ActiveMaterialID.GetValue(), Parameters.SelectedTriangles);
}
else
{
Pattern = FSelectiveTessellate::CreateConcentricRingsPatternFromMaterial(ResultMesh.Get(), SubdivisionsCount, Parameters.ActiveMaterialID.GetValue());
}
FDynamicMesh3 OutMesh;
FSelectiveTessellate Tessellator(ResultMesh.Get(), &OutMesh);
Tessellator.Progress = ProgressCancel;
Tessellator.SetPattern(Pattern.Get());
VerticesToDisplace = MakeUnique<TArray<int>>();
Tessellator.TessInfo.SelectedVertices = VerticesToDisplace.Get();
if (ensureMsgf(Tessellator.Validate() == EOperationValidationResult::Ok, TEXT("The tessellator parameters are invalid.")))
{
if (Tessellator.Compute())
{
*ResultMesh = MoveTemp(OutMesh);
}
}
} // EDisplaceMeshToolTriangleSelectionType::None but still tessellating only selected geometry
else if (bUsingSelection)
{
TUniquePtr<FTessellationPattern> Pattern =FSelectiveTessellate::CreateConcentricRingsTessellationPattern(ResultMesh.Get(), SubdivisionsCount, Parameters.SelectedTriangles);
FDynamicMesh3 OutMesh;
FSelectiveTessellate Tessellator(ResultMesh.Get(), &OutMesh);
Tessellator.Progress = ProgressCancel;
Tessellator.SetPattern(Pattern.Get());
VerticesToDisplace = MakeUnique<TArray<int>>();
Tessellator.TessInfo.SelectedVertices = VerticesToDisplace.Get();
if (ensureMsgf(Tessellator.Validate() == EOperationValidationResult::Ok, TEXT("The tessellator parameters are invalid.")))
{
if (Tessellator.Compute())
{
*ResultMesh = MoveTemp(OutMesh);
}
}
}
else
{
// Fall back to the uniform tessellation if either SelectionType is EDisplaceMeshToolTriangleSelectionType::None
// or not all the parameters were set for the other selection types.
FUniformTessellate Tessellator(ResultMesh.Get());
Tessellator.Progress = ProgressCancel;
Tessellator.TessellationNum = SubdivisionsCount;
if (ensureMsgf(Tessellator.Validate() == EOperationValidationResult::Ok, TEXT("The tessellator parameters are invalid.")))
{
Tessellator.Compute();
}
}
}
else
{
FUniformTessellate Tessellator(ResultMesh.Get());
Tessellator.Progress = ProgressCancel;
Tessellator.TessellationNum = SubdivisionsCount;
if (ensureMsgf(Tessellator.Validate() == EOperationValidationResult::Ok, TEXT("The tessellator parameters are invalid.")))
{
Tessellator.Compute();
}
}
}
else if (SubdivisionType == EDisplaceMeshToolSubdivisionType::PNTriangles)
{
FPNTriangles PNTriangles(ResultMesh.Get());
PNTriangles.Progress = ProgressCancel;
PNTriangles.TessellationLevel = SubdivisionsCount;
if (ensureMsgf(PNTriangles.Validate() == EOperationValidationResult::Ok, TEXT("The tessellator parameters are invalid.")))
{
PNTriangles.Compute();
}
}
else
{
// Unsupported subdivision type
checkNoEntry();
}
}
class FSubdivideMeshOpFactory : public IDynamicMeshOperatorFactory
{
public:
FSubdivideMeshOpFactory(FDynamicMesh3& SourceMeshIn,
const FSubdivideParameters& InParameters,
EDisplaceMeshToolSubdivisionType SubdivisionTypeIn)
:
SourceMesh(SourceMeshIn), Parameters(InParameters), SubdivisionType(SubdivisionTypeIn)
{
}
void SetSubdivisionType(EDisplaceMeshToolSubdivisionType SubdivisionTypeIn);
EDisplaceMeshToolSubdivisionType GetSubdivisionType() const;
void SetSubdivisionsCount(int SubdivisionsCountIn);
int GetSubdivisionsCount() const;
void SetWeightMap(TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMapIn);
void SetSelectionType(EDisplaceMeshToolTriangleSelectionType SelectionTypeIn)
{
Parameters.SelectionType = SelectionTypeIn;
}
// Materials
void SetActiveMaterialID(int ActiveMaterialID)
{
Parameters.ActiveMaterialID = ActiveMaterialID;
}
TUniquePtr<FDynamicMeshOperator> MakeNewOperator() final
{
return MakeUnique<FSubdivideMeshOp>(SourceMesh, Parameters, SubdivisionType);
}
private:
const FDynamicMesh3& SourceMesh;
FSubdivideParameters Parameters;
EDisplaceMeshToolSubdivisionType SubdivisionType;
};
void FSubdivideMeshOpFactory::SetSubdivisionType(EDisplaceMeshToolSubdivisionType SubdivisionTypeIn)
{
SubdivisionType = SubdivisionTypeIn;
}
EDisplaceMeshToolSubdivisionType FSubdivideMeshOpFactory::GetSubdivisionType() const
{
return SubdivisionType;
}
void FSubdivideMeshOpFactory::SetSubdivisionsCount(int SubdivisionsCountIn)
{
Parameters.SubdivisionsCount = SubdivisionsCountIn;
}
int FSubdivideMeshOpFactory::GetSubdivisionsCount() const
{
return Parameters.SubdivisionsCount;
}
void FSubdivideMeshOpFactory::SetWeightMap(TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMapIn)
{
Parameters.WeightMap = WeightMapIn;
}
// A collection of parameters to avoid having excess function parameters
struct DisplaceMeshParameters
{
float DisplaceIntensity = 0.0f;
int RandomSeed = 0;
UTexture2D* DisplacementMap = nullptr;
float SineWaveFrequency = 0.0f;
float SineWavePhaseShift = 0.0f;
FVector SineWaveDirection = { 0.0f, 0.0f, 0.0f };
bool bEnableFilter = false;
FVector FilterDirection = { 0.0f, 0.0f, 0.0f };
float FilterWidth = 0.0f;
FSampledScalarField2f DisplaceField;
TArray<FPerlinLayerProperties> PerlinLayerProperties;
bool bRecalculateNormals = true;
// Used in texture map displacement
int32 DisplacementMapChannel = 0;
float DisplacementMapBaseValue = 128.0/255; // i.e., what constitutes no displacement
FVector2f UVScale = FVector2f(1,1);
FVector2f UVOffset = FVector2f(0, 0);
// This gets used by worker threads, so do not try to change an existing curve- make
// a new one each time.
TSharedPtr<FRichCurve, ESPMode::ThreadSafe> AdjustmentCurve;
TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMap;
TFunction<float(const FVector3d&, const FIndexedWeightMap)> WeightMapQueryFunc;
TSharedPtr<TArray<int>, ESPMode::ThreadSafe> VerticesToDisplace; // if set, only displace vertices whose ids are part of the array
};
class FDisplaceMeshOp : public FDynamicMeshOperator
{
public:
FDisplaceMeshOp(TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> SourceMeshIn,
const DisplaceMeshParameters& DisplaceParametersIn,
EDisplaceMeshToolDisplaceType DisplacementTypeIn);
void CalculateResult(FProgressCancel* Progress) final;
private:
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> SourceMesh;
DisplaceMeshParameters Parameters;
EDisplaceMeshToolDisplaceType DisplacementType;
TArray<FVector3d> SourcePositions;
FMeshNormals SourceNormals;
TArray<FVector3d> DisplacedPositions;
};
FDisplaceMeshOp::FDisplaceMeshOp(TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> SourceMeshIn,
const DisplaceMeshParameters& DisplaceParametersIn,
EDisplaceMeshToolDisplaceType DisplacementTypeIn)
:
SourceMesh(MoveTemp(SourceMeshIn)),
Parameters(DisplaceParametersIn),
DisplacementType(DisplacementTypeIn)
{
}
void FDisplaceMeshOp::CalculateResult(FProgressCancel* Progress)
{
if (Progress && Progress->Cancelled()) return;
ResultMesh->Copy(*SourceMesh);
if (Progress && Progress->Cancelled()) return;
if (DisplacementType == EDisplaceMeshToolDisplaceType::DisplacementMap && !Parameters.DisplacementMap)
{
return;
}
SourceNormals = FMeshNormals(SourceMesh.Get());
SourceNormals.ComputeVertexNormals();
if (Progress && Progress->Cancelled()) return;
// cache initial positions
SourcePositions.SetNum(SourceMesh->MaxVertexID());
for (int vid : SourceMesh->VertexIndicesItr())
{
SourcePositions[vid] = SourceMesh->GetVertex(vid);
}
if (Progress && Progress->Cancelled()) return;
DisplacedPositions.SetNum(SourceMesh->MaxVertexID());
if (Progress && Progress->Cancelled()) return;
ComputeDisplacement::FDirectionalFilter DirectionalFilter{ Parameters.bEnableFilter,
FVector3d(Parameters.FilterDirection),
Parameters.FilterWidth };
double Intensity = Parameters.DisplaceIntensity;
TUniqueFunction<float(int32 vid, const FVector3d&)> WeightMapQueryFunc = [&](int32, const FVector3d&) { return 1.0f; };
if (Parameters.WeightMap.IsValid())
{
if (SourceMesh->IsCompactV() && SourceMesh->VertexCount() == Parameters.WeightMap->Num())
{
WeightMapQueryFunc = [&](int32 vid, const FVector3d& Pos) { return Parameters.WeightMap->GetValue(vid); };
}
else
{
// disable input query function as it uses expensive AABBTree lookup
//WeightMapQueryFunc = [&](int32 vid, const FVector3d& Pos) { return Parameters.WeightMapQueryFunc(Pos, *Parameters.WeightMap); };
WeightMapQueryFunc = [&](int32 vid, const FVector3d& Pos) { return SourceMesh->GetVertexUV(vid).X; };
}
}
auto IntensityFunc = [&](int32 vid, const FVector3d& Position, const FVector3d& Normal)
{
return Intensity * DirectionalFilter.FilterValue(Normal) * WeightMapQueryFunc(vid, Position);
};
// compute Displaced positions in PositionBuffer
switch (DisplacementType)
{
default:
case EDisplaceMeshToolDisplaceType::Constant:
ComputeDisplacement::Constant(*SourceMesh,
SourcePositions,
SourceNormals,
IntensityFunc,
DisplacedPositions,
Parameters.VerticesToDisplace.Get());
break;
case EDisplaceMeshToolDisplaceType::RandomNoise:
ComputeDisplacement::RandomNoise(*SourceMesh,
SourcePositions,
SourceNormals,
IntensityFunc,
Parameters.RandomSeed,
DisplacedPositions,
Parameters.VerticesToDisplace.Get());
break;
case EDisplaceMeshToolDisplaceType::PerlinNoise:
ComputeDisplacement::PerlinNoise(*SourceMesh,
SourcePositions,
SourceNormals,
IntensityFunc,
Parameters.PerlinLayerProperties,
Parameters.RandomSeed,
DisplacedPositions,
Parameters.VerticesToDisplace.Get());
break;
case EDisplaceMeshToolDisplaceType::DisplacementMap:
ComputeDisplacement::Map(*SourceMesh,
SourcePositions,
SourceNormals,
IntensityFunc,
Parameters.DisplaceField,
DisplacedPositions,
Parameters.DisplacementMapBaseValue,
Parameters.UVScale,
Parameters.UVOffset,
Parameters.VerticesToDisplace.Get(),
Parameters.AdjustmentCurve.Get());
break;
case EDisplaceMeshToolDisplaceType::SineWave:
ComputeDisplacement::Sine(*SourceMesh,
SourcePositions,
SourceNormals,
IntensityFunc,
Parameters.SineWaveFrequency,
Parameters.SineWavePhaseShift,
(FVector3d)Parameters.SineWaveDirection,
DisplacedPositions,
Parameters.VerticesToDisplace.Get());
break;
}
// update preview vertex positions
for (int vid : ResultMesh->VertexIndicesItr())
{
ResultMesh->SetVertex(vid, DisplacedPositions[vid]);
}
// recalculate normals
if (Parameters.bRecalculateNormals)
{
if (ResultMesh->HasAttributes())
{
FMeshNormals Normals(ResultMesh.Get());
FDynamicMeshNormalOverlay* NormalOverlay = ResultMesh->Attributes()->PrimaryNormals();
Normals.RecomputeOverlayNormals(NormalOverlay);
Normals.CopyToOverlay(NormalOverlay);
}
else
{
FMeshNormals::QuickComputeVertexNormals(*ResultMesh);
}
}
}
class FDisplaceMeshOpFactory : public IDynamicMeshOperatorFactory
{
public:
FDisplaceMeshOpFactory(TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe>& SourceMeshIn,
const DisplaceMeshParameters& DisplaceParametersIn,
EDisplaceMeshToolDisplaceType DisplacementTypeIn )
:
SourceMesh(SourceMeshIn)
{
SetIntensity(DisplaceParametersIn.DisplaceIntensity);
SetRandomSeed(DisplaceParametersIn.RandomSeed);
SetDisplacementMap(DisplaceParametersIn.DisplacementMap, DisplaceParametersIn.DisplacementMapChannel); // Calls UpdateMap
SetFrequency(DisplaceParametersIn.SineWaveFrequency);
SetPhaseShift(DisplaceParametersIn.SineWavePhaseShift);
SetSineWaveDirection(DisplaceParametersIn.SineWaveDirection);
SetEnableDirectionalFilter(DisplaceParametersIn.bEnableFilter);
SetFilterDirection(DisplaceParametersIn.FilterDirection);
SetFilterFalloffWidth(DisplaceParametersIn.FilterWidth);
SetPerlinNoiseLayerProperties(DisplaceParametersIn.PerlinLayerProperties);
SetDisplacementType(DisplacementTypeIn);
SetVerticesToDisplace(DisplaceParametersIn.VerticesToDisplace);
Parameters.WeightMap = DisplaceParametersIn.WeightMap;
Parameters.WeightMapQueryFunc = DisplaceParametersIn.WeightMapQueryFunc;
Parameters.DisplacementMapBaseValue = DisplaceParametersIn.DisplacementMapBaseValue;
Parameters.UVScale = DisplaceParametersIn.UVScale;
Parameters.UVOffset = DisplaceParametersIn.UVOffset;
Parameters.AdjustmentCurve = DisplaceParametersIn.AdjustmentCurve;
}
void SetIntensity(float IntensityIn);
void SetRandomSeed(int RandomSeedIn);
void SetDisplacementMap(UTexture2D* DisplacementMapIn, int32 ChannelIn);
void SetDisplacementMapUVAdjustment(const FVector2f& UVScale, const FVector2f& UVOffset);
void SetDisplacementMapBaseValue(float DisplacementMapBaseValue);
void SetAdjustmentCurve(UCurveFloat* CurveFloat);
void SetFrequency(float FrequencyIn);
void SetPhaseShift(float PhaseShiftIn);
void SetSineWaveDirection(const FVector& Direction);
void SetDisplacementType(EDisplaceMeshToolDisplaceType TypeIn);
void SetEnableDirectionalFilter(bool EnableDirectionalFilter);
void SetFilterDirection(const FVector& Direction);
void SetFilterFalloffWidth(float FalloffWidth);
void SetPerlinNoiseLayerProperties(const TArray<FPerlinLayerProperties>& PerlinLayerProperties);
void SetWeightMap(TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMap);
void SetRecalculateNormals(bool bRecalculateNormals);
void SetVerticesToDisplace(TSharedPtr<TArray<int>, ESPMode::ThreadSafe> VerticesToDisplace);
TUniquePtr<FDynamicMeshOperator> MakeNewOperator() final
{
return MakeUnique<FDisplaceMeshOp>(SourceMesh, Parameters, DisplacementType);
}
private:
void UpdateMap();
DisplaceMeshParameters Parameters;
EDisplaceMeshToolDisplaceType DisplacementType;
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe>& SourceMesh;
};
void FDisplaceMeshOpFactory::SetIntensity(float IntensityIn)
{
Parameters.DisplaceIntensity = IntensityIn;
}
void FDisplaceMeshOpFactory::SetRandomSeed(int RandomSeedIn)
{
Parameters.RandomSeed = RandomSeedIn;
}
void FDisplaceMeshOpFactory::SetDisplacementMap(UTexture2D* DisplacementMapIn, int32 ChannelIn)
{
Parameters.DisplacementMap = DisplacementMapIn;
Parameters.DisplacementMapChannel = ChannelIn;
// Note that we do the update even if we got the same pointer, because the texture
// may have been changed in the editor.
UpdateMap();
}
void FDisplaceMeshOpFactory::SetDisplacementMapUVAdjustment(const FVector2f& UVScale, const FVector2f& UVOffset)
{
Parameters.UVScale = UVScale;
Parameters.UVOffset = UVOffset;
}
void FDisplaceMeshOpFactory::SetDisplacementMapBaseValue(float DisplacementMapBaseValue)
{
// We could bake this into the displacement field, but that would require calling UpdateMap with
// every slider change, which is slow. So we'll just pass this down to the calculation.
Parameters.DisplacementMapBaseValue = DisplacementMapBaseValue;
}
void FDisplaceMeshOpFactory::SetAdjustmentCurve(UCurveFloat* CurveFloat)
{
Parameters.AdjustmentCurve = CurveFloat ? TSharedPtr<FRichCurve, ESPMode::ThreadSafe>(
static_cast<FRichCurve*>(CurveFloat->FloatCurve.Duplicate()))
: nullptr;
}
void FDisplaceMeshOpFactory::SetFrequency(float FrequencyIn)
{
Parameters.SineWaveFrequency = FrequencyIn;
}
void FDisplaceMeshOpFactory::SetPhaseShift(float PhaseShiftIn)
{
Parameters.SineWavePhaseShift = PhaseShiftIn;
}
void FDisplaceMeshOpFactory::SetSineWaveDirection(const FVector& Direction)
{
Parameters.SineWaveDirection = Direction.GetSafeNormal();
}
void FDisplaceMeshOpFactory::SetDisplacementType(EDisplaceMeshToolDisplaceType TypeIn)
{
DisplacementType = TypeIn;
}
void FDisplaceMeshOpFactory::UpdateMap()
{
if (Parameters.DisplacementMap == nullptr ||
Parameters.DisplacementMap->GetPlatformData() == nullptr ||
Parameters.DisplacementMap->GetPlatformData()->Mips.Num() < 1)
{
Parameters.DisplaceField = FSampledScalarField2f();
Parameters.DisplaceField.GridValues.AssignAll(0);
return;
}
TImageBuilder<FVector4f> DisplacementMapValues;
if (!UE::AssetUtils::ReadTexture(Parameters.DisplacementMap, DisplacementMapValues, false))
{
Parameters.DisplaceField = FSampledScalarField2f();
Parameters.DisplaceField.GridValues.AssignAll(0);
}
else
{
const FImageDimensions DisplacementMapDimensions = DisplacementMapValues.GetDimensions();
int64 TextureWidth = DisplacementMapDimensions.GetWidth();
int64 TextureHeight = DisplacementMapDimensions.GetHeight();
Parameters.DisplaceField.Resize(TextureWidth, TextureHeight, 0.0f);
// Note that the height of the texture will not be 1.0 if it was not square. This should be kept in mind when sampling it later.
Parameters.DisplaceField.SetCellSize(1.0f / (float)TextureWidth);
for (int64 y = 0; y < TextureHeight; ++y)
{
for (int64 x = 0; x < TextureWidth; ++x)
{
Parameters.DisplaceField.GridValues[y * TextureWidth + x] =
DisplacementMapValues.GetPixel(y * TextureWidth + x)[Parameters.DisplacementMapChannel];
}
}
}
}
void FDisplaceMeshOpFactory::SetEnableDirectionalFilter(bool EnableDirectionalFilter)
{
Parameters.bEnableFilter = EnableDirectionalFilter;
}
void FDisplaceMeshOpFactory::SetFilterDirection(const FVector& Direction)
{
Parameters.FilterDirection = Direction.GetSafeNormal();
}
void FDisplaceMeshOpFactory::SetFilterFalloffWidth(float FalloffWidth)
{
Parameters.FilterWidth = FalloffWidth;
}
void FDisplaceMeshOpFactory::SetPerlinNoiseLayerProperties(const TArray<FPerlinLayerProperties>& LayerProperties )
{
Parameters.PerlinLayerProperties = LayerProperties;
}
void FDisplaceMeshOpFactory::SetWeightMap(TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> WeightMap)
{
Parameters.WeightMap = WeightMap;
}
void FDisplaceMeshOpFactory::SetRecalculateNormals(bool RecalcNormalsIn)
{
Parameters.bRecalculateNormals = RecalcNormalsIn;
}
void FDisplaceMeshOpFactory::SetVerticesToDisplace(TSharedPtr<TArray<int>, ESPMode::ThreadSafe> VerticesToDisplace)
{
Parameters.VerticesToDisplace = VerticesToDisplace;
}
} // namespace
/*
* ToolBuilder
*/
USingleTargetWithSelectionTool* UDisplaceMeshToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
return NewObject<UDisplaceMeshTool>(SceneState.ToolManager);
}
const FToolTargetTypeRequirements& UDisplaceMeshToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UMaterialProvider::StaticClass(),
UMeshDescriptionProvider::StaticClass(),
UMeshDescriptionCommitter::StaticClass(),
UPrimitiveComponentBackedTarget::StaticClass()
});
return TypeRequirements;
}
/*
* Tool
*/
TArray<FString> UDisplaceMeshCommonProperties::GetWeightMapsFunc()
{
return WeightMapsList;
}
void UDisplaceMeshTool::Setup()
{
using namespace DisplaceMeshToolLocals;
UInteractiveTool::Setup();
// UInteractiveToolPropertySets
NoiseProperties = NewObject<UDisplaceMeshPerlinNoiseProperties>();
NoiseProperties->RestoreProperties(this);
CommonProperties = NewObject<UDisplaceMeshCommonProperties>();
CommonProperties->RestoreProperties(this);
DirectionalFilterProperties = NewObject<UDisplaceMeshDirectionalFilterProperties>();
DirectionalFilterProperties->RestoreProperties(this);
TextureMapProperties = NewObject<UDisplaceMeshTextureMapProperties>();
TextureMapProperties->RestoreProperties(this);
SineWaveProperties = NewObject<UDisplaceMeshSineWaveProperties>();
SineWaveProperties->RestoreProperties(this);
SelectiveTessellationProperties = NewObject<USelectiveTessellationProperties>();
SelectiveTessellationProperties->RestoreProperties(this);
if (TextureMapProperties->DisplacementMap != nullptr && TextureMapProperties->DisplacementMap->IsValidLowLevel() == false)
{
TextureMapProperties->DisplacementMap = nullptr;
}
if (TextureMapProperties->AdjustmentCurve == nullptr)
{
// if curve is null, create from default
TextureMapProperties->AdjustmentCurve = ToolSetupUtil::GetContrastAdjustmentCurve(GetToolManager());
}
// populate weight maps list
const FMeshDescription* MeshDescription = UE::ToolTarget::GetMeshDescription(Target);
TArray<FName> WeightMaps;
UE::WeightMaps::FindVertexWeightMaps(MeshDescription, WeightMaps);
CommonProperties->WeightMapsList.Add(TEXT("None"));
for (FName Name : WeightMaps)
{
CommonProperties->WeightMapsList.Add(Name.ToString());
}
if (WeightMaps.Contains(CommonProperties->WeightMap) == false) // discard restored value if it doesn't apply
{
CommonProperties->WeightMap = FName(CommonProperties->WeightMapsList[0]);
}
UpdateActiveWeightMap();
// create dynamic mesh component to use for live preview
FActorSpawnParameters SpawnInfo;
PreviewMeshActor = TargetWorld->SpawnActor<AInternalToolFrameworkActor>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo);
DynamicMeshComponent = NewObject<UDynamicMeshComponent>(PreviewMeshActor);
DynamicMeshComponent->SetupAttachment(PreviewMeshActor->GetRootComponent());
DynamicMeshComponent->RegisterComponent();
DynamicMeshComponent->SetWorldTransform((FTransform)UE::ToolTarget::GetLocalToWorldTransform(Target));
DynamicMeshComponent->bExplicitShowWireframe = CommonProperties->bShowWireframe;
ToolSetupUtil::ApplyRenderingConfigurationToPreview(DynamicMeshComponent, Target);
// transfer materials
FComponentMaterialSet MaterialSet = UE::ToolTarget::GetMaterialSet(Target);
for (int k = 0; k < MaterialSet.Materials.Num(); ++k)
{
DynamicMeshComponent->SetMaterial(k, MaterialSet.Materials[k]);
}
DynamicMeshComponent->SetTangentsType(EDynamicMeshComponentTangentsMode::AutoCalculated);
DynamicMeshComponent->SetMesh(UE::ToolTarget::GetDynamicMeshCopy(Target));
// retrieve the selected triangles
TArray<int> TrianglesSelectedInMesh;
if (HasGeometrySelection())
{
const FGeometrySelection& InputSelection = GetGeometrySelection();
UE::Geometry::EnumerateSelectionTriangles(InputSelection, *DynamicMeshComponent->GetMesh(),
[&](int32 TriangleID){ TrianglesSelectedInMesh.Add(TriangleID); });
}
else //if the whole object is selected, SelectedTriangles will be all the triangles in the Object
{
for (int32 TriangleID : DynamicMeshComponent->GetMesh()->TriangleIndicesItr())
{
TrianglesSelectedInMesh.Add(TriangleID);
}
}
NumTrianglesInSelection = TrianglesSelectedInMesh.Num();
OriginalMesh.Copy(*DynamicMeshComponent->GetMesh());
OriginalMeshSpatial.SetMesh(&OriginalMesh, true);
DisplaceMeshParameters Parameters;
Parameters.DisplaceIntensity = CommonProperties->DisplaceIntensity;
Parameters.RandomSeed = CommonProperties->RandomSeed;
Parameters.DisplacementMap = TextureMapProperties->DisplacementMap;
Parameters.bRecalculateNormals = TextureMapProperties->bRecalcNormals;
Parameters.SineWaveFrequency = SineWaveProperties->SineWaveFrequency;
Parameters.SineWavePhaseShift = SineWaveProperties->SineWavePhaseShift;
Parameters.SineWaveDirection = SineWaveProperties->SineWaveDirection.GetSafeNormal();
Parameters.bEnableFilter = DirectionalFilterProperties->bEnableFilter;
Parameters.FilterDirection = DirectionalFilterProperties->FilterDirection.GetSafeNormal();
Parameters.FilterWidth = DirectionalFilterProperties->FilterWidth;
Parameters.PerlinLayerProperties = NoiseProperties->PerlinLayerProperties;
Parameters.WeightMap = ActiveWeightMap;
Parameters.WeightMapQueryFunc = [this](const FVector3d& Position, const FIndexedWeightMap& WeightMap) { return WeightMapQuery(Position, WeightMap); };
Displacer = MakeUnique<FDisplaceMeshOpFactory>(SubdividedMesh, Parameters, CommonProperties->DisplacementType);
// hide input StaticMeshComponent
UE::ToolTarget::HideSourceObject(Target);
// Fetch the list of the materials if available
SelectiveTessellationProperties->MaterialIDList.Reset();
for (int32 Idx = 0; Idx < MaterialSet.Materials.Num(); ++Idx)
{
UMaterialInterface* Mat = MaterialSet.Materials[Idx];
FString MatName = (Mat != nullptr) ? Mat->GetName() : "(none)";
FString UseName = FString::Printf(TEXT("[%d] %s"), Idx, *MatName);
SelectiveTessellationProperties->MaterialIDList.Add(UseName);
}
if (SelectiveTessellationProperties->MaterialIDList.IsEmpty())
{
SelectiveTessellationProperties->ActiveMaterial = NAME_None;
}
else if (SelectiveTessellationProperties->MaterialIDList.Contains(SelectiveTessellationProperties->ActiveMaterial.ToString()) == false)
{
SelectiveTessellationProperties->ActiveMaterial = NAME_None;
}
// initialize our properties
ToolPropertyObjects.Add(this);
AddToolPropertySource(CommonProperties);
SetToolPropertySourceEnabled(CommonProperties, true);
AddToolPropertySource(DirectionalFilterProperties);
SetToolPropertySourceEnabled(DirectionalFilterProperties, true);
AddToolPropertySource(TextureMapProperties);
SetToolPropertySourceEnabled(TextureMapProperties, CommonProperties->DisplacementType == EDisplaceMeshToolDisplaceType::DisplacementMap);
AddToolPropertySource(SineWaveProperties);
SetToolPropertySourceEnabled(SineWaveProperties, CommonProperties->DisplacementType == EDisplaceMeshToolDisplaceType::SineWave);
AddToolPropertySource(SelectiveTessellationProperties);
SetToolPropertySourceEnabled(SelectiveTessellationProperties, CommonProperties->SubdivisionType == EDisplaceMeshToolSubdivisionType::Flat);
AddToolPropertySource(NoiseProperties);
SetToolPropertySourceEnabled(NoiseProperties, CommonProperties->DisplacementType == EDisplaceMeshToolDisplaceType::PerlinNoise);
// Set up a callback for when the type of displacement changes
CommonProperties->WatchProperty(CommonProperties->DisplacementType,
[this](EDisplaceMeshToolDisplaceType NewType)
{
SetToolPropertySourceEnabled(NoiseProperties, (NewType == EDisplaceMeshToolDisplaceType::PerlinNoise));
SetToolPropertySourceEnabled(SineWaveProperties, (NewType == EDisplaceMeshToolDisplaceType::SineWave));
SetToolPropertySourceEnabled(TextureMapProperties, (NewType == EDisplaceMeshToolDisplaceType::DisplacementMap));
} );
CommonProperties->WatchProperty(CommonProperties->SubdivisionType,
[this](EDisplaceMeshToolSubdivisionType NewType)
{
SetToolPropertySourceEnabled(SelectiveTessellationProperties, (NewType == EDisplaceMeshToolSubdivisionType::Flat));
} );
ValidateSubdivisions();
FSubdivideParameters SubParameters;
SubParameters.SubdivisionsCount = CommonProperties->Subdivisions;
SubParameters.WeightMap = ActiveWeightMap;
SubParameters.SelectionType = SelectiveTessellationProperties->SelectionType;
SubParameters.SelectedTriangles = MoveTemp(TrianglesSelectedInMesh);
if (SelectiveTessellationProperties->ActiveMaterial.IsNone() == false)
{
int32 Index = SelectiveTessellationProperties->MaterialIDList.Find(SelectiveTessellationProperties->ActiveMaterial.ToString());
SubParameters.ActiveMaterialID = Index;
}
Subdivider = MakeUnique<FSubdivideMeshOpFactory>(OriginalMesh, SubParameters, CommonProperties->SubdivisionType);
MeshStatistics = NewObject<UMeshStatisticsProperties>(this);
MeshStatistics->Update(OriginalMesh);
AddToolPropertySource(MeshStatistics);
StartComputation();
SetToolDisplayName(LOCTEXT("ToolName", "Displace"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartDisplaceMesh", "Subdivide and Displace the input mesh using different noise functions and maps"),
EToolMessageLevel::UserNotification);
}
void UDisplaceMeshTool::OnShutdown(EToolShutdownType ShutdownType)
{
// unhook any active listener for contrast curve
DisconnectActiveContrastCurveTarget();
CommonProperties->SaveProperties(this);
NoiseProperties->SaveProperties(this);
DirectionalFilterProperties->SaveProperties(this);
SineWaveProperties->SaveProperties(this);
TextureMapProperties->SaveProperties(this);
SelectiveTessellationProperties->SaveProperties(this);
if (DynamicMeshComponent != nullptr)
{
UE::ToolTarget::ShowSourceObject(Target);
if (ShutdownType == EToolShutdownType::Accept)
{
// this block bakes the modified DynamicMeshComponent back into the StaticMeshComponent inside an undo transaction
GetToolManager()->BeginUndoTransaction(LOCTEXT("DisplaceMeshToolTransactionName", "Displace Mesh"));
// if we are applying a map and not recalculating normals, we need to make sure normals recalculation is disabled
// on the underlying StaticMesh Asset, or it will run on the Bake() below and the output result will not be the same as the preview
if (CommonProperties->DisplacementType == EDisplaceMeshToolDisplaceType::DisplacementMap && TextureMapProperties->bRecalcNormals == false)
{
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(UE::ToolTarget::GetTargetComponent(Target));
if (StaticMeshComponent)
{
if (UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh())
{
StaticMesh->Modify();
// disable auto-generated normals and tangents build settings
UE::MeshDescription::FStaticMeshBuildSettingChange SettingsChange;
SettingsChange.AutoGeneratedNormals = UE::MeshDescription::EBuildSettingBoolChange::Disable;
UE::MeshDescription::ConfigureBuildSettings(StaticMesh, 0, SettingsChange);
}
}
}
DynamicMeshComponent->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
UE::ToolTarget::CommitDynamicMeshUpdate(Target, ReadMesh, CommonProperties->Subdivisions > 0);
});
GetToolManager()->EndUndoTransaction();
}
DynamicMeshComponent->UnregisterComponent();
DynamicMeshComponent->DestroyComponent();
DynamicMeshComponent = nullptr;
}
if (PreviewMeshActor != nullptr)
{
PreviewMeshActor->Destroy();
PreviewMeshActor = nullptr;
}
}
void UDisplaceMeshTool::ValidateSubdivisions()
{
if (CommonProperties->bDisableSizeWarning)
{
GetToolManager()->DisplayMessage(FText::GetEmpty(), EToolMessageLevel::UserWarning);
return;
}
bool bIsInitialized = (Subdivider != nullptr);
constexpr int MaxTriangles = 3000000;
double NumTriangles = NumTrianglesInSelection;
int MaxSubdivisions = (int)(FMath::Sqrt(MaxTriangles/NumTriangles) - 1);
if (CommonProperties->Subdivisions > MaxSubdivisions)
{
if (bIsInitialized) // only show warning after initial tool startup
{
FText WarningText = FText::Format(LOCTEXT("SubdivisionsTooHigh", "Desired number of Subdivisions ({0}) exceeds maximum number ({1}) for a mesh of this number of triangles."),
FText::AsNumber(CommonProperties->Subdivisions),
FText::AsNumber(MaxSubdivisions));
GetToolManager()->DisplayMessage(WarningText, EToolMessageLevel::UserWarning);
}
CommonProperties->Subdivisions = MaxSubdivisions;
}
else
{
FText ClearWarningText;
GetToolManager()->DisplayMessage(ClearWarningText, EToolMessageLevel::UserWarning);
}
if (CommonProperties->Subdivisions < 0)
{
CommonProperties->Subdivisions = 0;
}
}
#if WITH_EDITOR
void UDisplaceMeshTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
using namespace DisplaceMeshToolLocals;
if (AreAllTargetsValid() == false)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidTargets", "Target mesh is no longer valid"), EToolMessageLevel::UserWarning);
return;
}
if (PropertySet && Property)
{
FDisplaceMeshOpFactory* DisplacerDownCast = static_cast<FDisplaceMeshOpFactory*>(Displacer.Get());
FSubdivideMeshOpFactory* SubdividerDownCast = static_cast<FSubdivideMeshOpFactory*>(Subdivider.Get());
const FString PropertySetName = PropertySet->GetFName().GetPlainNameString();
const FName PropName = Property->GetFName();
bNeedsDisplaced = true;
if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, SubdivisionType))
{
if (CommonProperties->SubdivisionType != SubdividerDownCast->GetSubdivisionType())
{
SubdividerDownCast->SetSubdivisionType(CommonProperties->SubdivisionType);
bNeedsSubdivided = true;
}
else
{
return;
}
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, Subdivisions))
{
ValidateSubdivisions();
if (CommonProperties->Subdivisions != SubdividerDownCast->GetSubdivisionsCount())
{
SubdividerDownCast->SetSubdivisionsCount(CommonProperties->Subdivisions);
bNeedsSubdivided = true;
}
else
{
return;
}
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, RandomSeed))
{
DisplacerDownCast->SetRandomSeed(CommonProperties->RandomSeed);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, DisplacementType))
{
DisplacerDownCast->SetDisplacementType(CommonProperties->DisplacementType);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, DisplaceIntensity))
{
DisplacerDownCast->SetIntensity(CommonProperties->DisplaceIntensity);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, bShowWireframe))
{
DynamicMeshComponent->bExplicitShowWireframe = CommonProperties->bShowWireframe;
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshSineWaveProperties, SineWaveFrequency))
{
DisplacerDownCast->SetFrequency(SineWaveProperties->SineWaveFrequency);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshSineWaveProperties, SineWavePhaseShift))
{
DisplacerDownCast->SetPhaseShift(SineWaveProperties->SineWavePhaseShift);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, DisplacementMap))
{
if (TextureMapProperties->DisplacementMap != nullptr
&& TextureMapProperties->DisplacementMap->VirtualTextureStreaming)
{
GetToolManager()->DisplayMessage(
LOCTEXT("DisplaceToolVirtualTextureMessage", "Virtual Texture must be disabled on the selected Texture2D to use it as a Displacement Map in this Tool"),
EToolMessageLevel::UserWarning);
}
else
{
GetToolManager()->DisplayMessage(FText::GetEmpty(), EToolMessageLevel::UserWarning);
}
DisplacerDownCast->SetDisplacementMap(TextureMapProperties->DisplacementMap, static_cast<int32>(TextureMapProperties->Channel));
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, Channel))
{
DisplacerDownCast->SetDisplacementMap(TextureMapProperties->DisplacementMap, static_cast<int32>(TextureMapProperties->Channel));
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, DisplacementMapBaseValue))
{
DisplacerDownCast->SetDisplacementMapBaseValue(TextureMapProperties->DisplacementMapBaseValue);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, bRecalcNormals))
{
DisplacerDownCast->SetRecalculateNormals(TextureMapProperties->bRecalcNormals);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, bApplyAdjustmentCurve)
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, AdjustmentCurve))
{
DisplacerDownCast->SetAdjustmentCurve(TextureMapProperties->bApplyAdjustmentCurve ? TextureMapProperties->AdjustmentCurve : nullptr);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, WeightMap)
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshCommonProperties, bInvertWeightMap))
{
UpdateActiveWeightMap();
SubdividerDownCast->SetWeightMap(ActiveWeightMap);
DisplacerDownCast->SetWeightMap(ActiveWeightMap);
bNeedsSubdivided = true;
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshDirectionalFilterProperties, bEnableFilter))
{
DisplacerDownCast->SetEnableDirectionalFilter(DirectionalFilterProperties->bEnableFilter);
}
else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshDirectionalFilterProperties, FilterWidth))
{
DisplacerDownCast->SetFilterFalloffWidth(DirectionalFilterProperties->FilterWidth);
}
else if (PropertySet == NoiseProperties)
{
DisplacerDownCast->SetPerlinNoiseLayerProperties(NoiseProperties->PerlinLayerProperties);
}
// The FName we get for the individual vector elements are all the same, whereas resetting with the "revert
// to default" arrow gets us the name of the vector itself. We'll just update all of them if any of them
// change.
else if (PropName == "X" || PropName == "Y" || PropName == "Z"
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshDirectionalFilterProperties, FilterDirection)
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshSineWaveProperties, SineWaveDirection)
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, UVScale)
|| PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, UVOffset))
{
DisplacerDownCast->SetFilterDirection(DirectionalFilterProperties->FilterDirection);
DisplacerDownCast->SetSineWaveDirection(SineWaveProperties->SineWaveDirection);
DisplacerDownCast->SetDisplacementMapUVAdjustment(FVector2f(TextureMapProperties->UVScale), FVector2f(TextureMapProperties->UVOffset)); // LWC_TODO: Precision loss
}
else if (PropName == GET_MEMBER_NAME_CHECKED(USelectiveTessellationProperties, SelectionType))
{
SubdividerDownCast->SetSelectionType(SelectiveTessellationProperties->SelectionType);
bNeedsSubdivided = true;
}
else if (PropName == GET_MEMBER_NAME_CHECKED(USelectiveTessellationProperties, ActiveMaterial))
{
int32 Index = SelectiveTessellationProperties->MaterialIDList.Find(SelectiveTessellationProperties->ActiveMaterial.ToString());
SubdividerDownCast->SetActiveMaterialID(Index);
bNeedsSubdivided = true;
}
StartComputation();
}
}
#endif
void UDisplaceMeshTool::UpdateActiveContrastCurveTarget()
{
using namespace DisplaceMeshToolLocals;
// if user resets the AdjustmentCurve field, it will go to nullptr, in this case we will force it
// back to a new default curve
if (TextureMapProperties->AdjustmentCurve == nullptr)
{
using namespace DisplaceMeshToolLocals;
TextureMapProperties->AdjustmentCurve = ToolSetupUtil::GetContrastAdjustmentCurve(GetToolManager());
bNeedsDisplaced = true;
}
#if WITH_EDITORONLY_DATA
// if the AdjustmentCurve changes, then we need to change which one we are listening to for CurveUpdate events
if (TextureMapProperties->AdjustmentCurve != ActiveContrastCurveTarget)
{
DisconnectActiveContrastCurveTarget();
if (TextureMapProperties->AdjustmentCurve != nullptr)
{
ActiveContrastCurveTarget = TextureMapProperties->AdjustmentCurve;
ActiveContrastCurveListenerHandle = ActiveContrastCurveTarget->OnUpdateCurve.AddWeakLambda(this,
[this](UCurveBase* Curve, EPropertyChangeType::Type ChangeType) {
if (TextureMapProperties->bApplyAdjustmentCurve)
{
FDisplaceMeshOpFactory* DisplacerDownCast = static_cast<FDisplaceMeshOpFactory*>(Displacer.Get());
DisplacerDownCast->SetAdjustmentCurve(TextureMapProperties->AdjustmentCurve);
bNeedsDisplaced = true;
StartComputation();
}
});
}
}
#endif
}
void UDisplaceMeshTool::DisconnectActiveContrastCurveTarget()
{
using namespace DisplaceMeshToolLocals;
#if WITH_EDITORONLY_DATA
if (ActiveContrastCurveTarget != nullptr)
{
ActiveContrastCurveTarget->OnUpdateCurve.Remove(ActiveContrastCurveListenerHandle);
ActiveContrastCurveListenerHandle = FDelegateHandle();
ActiveContrastCurveTarget = nullptr;
FDisplaceMeshOpFactory* DisplacerDownCast = static_cast<FDisplaceMeshOpFactory*>(Displacer.Get());
DisplacerDownCast->SetAdjustmentCurve(nullptr);
}
#endif
}
void UDisplaceMeshTool::OnTick(float DeltaTime)
{
UpdateActiveContrastCurveTarget();
AdvanceComputation();
}
void UDisplaceMeshTool::StartComputation()
{
if ( bNeedsSubdivided )
{
if (SubdivideTask)
{
SubdivideTask->CancelAndDelete();
}
SubdividedMesh = nullptr;
SubdivideTask = new FAsyncTaskExecuterWithAbort<TModelingOpTask<FDynamicMeshOperator>>(Subdivider->MakeNewOperator());
SubdivideTask->StartBackgroundTask();
bNeedsSubdivided = false;
DynamicMeshComponent->SetOverrideRenderMaterial(ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
}
if (bNeedsDisplaced && DisplaceTask)
{
DisplaceTask->CancelAndDelete();
DisplaceTask = nullptr;
DynamicMeshComponent->SetOverrideRenderMaterial(ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
}
AdvanceComputation();
}
void UDisplaceMeshTool::AdvanceComputation()
{
using namespace DisplaceMeshToolLocals;
if (SubdivideTask && SubdivideTask->IsDone())
{
TUniquePtr<FDynamicMeshOperator> SubOp = SubdivideTask->GetTask().ExtractOperator();
FSubdivideMeshOp* SubOpDownCast = static_cast<FSubdivideMeshOp*>(SubOp.Get());
SubdividedMesh = TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe>(SubOp->ExtractResult().Release());
VerticesToDisplace = TSharedPtr<TArray<int>, ESPMode::ThreadSafe>(SubOpDownCast->ExtractVerticesToDisplace().Release());
MeshStatistics->Update(*SubdividedMesh);
delete SubdivideTask;
SubdivideTask = nullptr;
}
if (SubdividedMesh && bNeedsDisplaced)
{
// force update of contrast curve
FDisplaceMeshOpFactory* DisplacerDownCast = static_cast<FDisplaceMeshOpFactory*>(Displacer.Get());
DisplacerDownCast->SetAdjustmentCurve(TextureMapProperties->bApplyAdjustmentCurve ? TextureMapProperties->AdjustmentCurve : nullptr);
DisplacerDownCast->SetVerticesToDisplace(VerticesToDisplace);
DisplaceTask = new FAsyncTaskExecuterWithAbort<TModelingOpTask<FDynamicMeshOperator>>(Displacer->MakeNewOperator());
DisplaceTask->StartBackgroundTask();
bNeedsDisplaced = false;
}
if (DisplaceTask && DisplaceTask->IsDone())
{
TUniquePtr<FDynamicMesh3> DisplacedMesh = DisplaceTask->GetTask().ExtractOperator()->ExtractResult();
MeshStatistics->Update(*SubdividedMesh);
delete DisplaceTask;
DisplaceTask = nullptr;
DynamicMeshComponent->ClearOverrideRenderMaterial();
DynamicMeshComponent->GetMesh()->Copy(*DisplacedMesh);
DynamicMeshComponent->NotifyMeshUpdated();
GetToolManager()->PostInvalidation();
}
}
void UDisplaceMeshTool::UpdateActiveWeightMap()
{
if (CommonProperties->WeightMap == FName(TEXT("None")))
{
ActiveWeightMap = nullptr;
}
else
{
TSharedPtr<FIndexedWeightMap, ESPMode::ThreadSafe> NewWeightMap = MakeShared<FIndexedWeightMap, ESPMode::ThreadSafe>();
const FMeshDescription* MeshDescription = UE::ToolTarget::GetMeshDescription(Target);
bool bFound = UE::WeightMaps::GetVertexWeightMap(MeshDescription, CommonProperties->WeightMap, *NewWeightMap, 1.0f);
if (CommonProperties->bInvertWeightMap)
{
NewWeightMap->InvertWeightMap();
}
// We'll check for invalid weightmaps here and reset to None if they aren't valid. This helps in cases where values are set externally,
// for example from preset loading, where the weightmap values incoming might not have any relation to the current target mesh.
if (bFound)
{
ActiveWeightMap = NewWeightMap;
}
else
{
CommonProperties->WeightMap = FName(CommonProperties->WeightMapsList[0]);
ActiveWeightMap = nullptr;
}
}
}
float UDisplaceMeshTool::WeightMapQuery(const FVector3d& Position, const FIndexedWeightMap& WeightMap) const
{
double NearDistSqr;
int32 NearTID = OriginalMeshSpatial.FindNearestTriangle(Position, NearDistSqr);
if (NearTID < 0)
{
return 1.0f;
}
FDistPoint3Triangle3d Distance = TMeshQueries<FDynamicMesh3>::TriangleDistance(OriginalMesh, NearTID, Position);
FIndex3i Tri = OriginalMesh.GetTriangle(NearTID);
return WeightMap.GetInterpValue(Tri, Distance.TriangleBaryCoords);
}
#undef LOCTEXT_NAMESPACE
#include "Tests/DisplaceMeshTool_Tests.inl"
#include UE_INLINE_GENERATED_CPP_BY_NAME(DisplaceMeshTool)