// 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 void ParallelDisplace(const FDynamicMesh3& Mesh, const TArray& Positions, const FMeshNormals& Normals, TArray& DisplacedPositions, TArray* 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& Positions, const FMeshNormals& Normals, TFunctionRef IntensityFunc, TArray& DisplacedPositions, TArray* 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& Positions, const FMeshNormals& Normals, TFunctionRef IntensityFunc, int RandomSeed, TArray& DisplacedPositions, TArray* 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& Positions, const FMeshNormals& Normals, TFunctionRef IntensityFunc, const TArray& PerlinLayerProperties, int RandomSeed, TArray& DisplacedPositions, TArray* 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& Positions, const FMeshNormals& Normals, TFunctionRef IntensityFunc, const FSampledScalarField2f& DisplaceField, TArray& DisplacedPositions, float DisplaceFieldBaseValue = 128.0/255, // value that corresponds to zero displacement FVector2f UVScale = FVector2f(1, 1), FVector2f UVOffset = FVector2f(0,0), TArray* 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 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 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& Positions, const FMeshNormals& Normals, TFunctionRef IntensityFunc, double Frequency, double PhaseShift, const FVector3d& Direction, TArray& DisplacedPositions, TArray* 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 WeightMap = nullptr; TArray SelectedTriangles; // Optional selection parameters TOptional SelectionType; // Material selection variables (SelectionType == EDisplaceMeshToolTriangleSelectionType::Material) TOptional ActiveMaterialID; }; class FSubdivideMeshOp : public FDynamicMeshOperator { public: FSubdivideMeshOp(const FDynamicMesh3& SourceMesh, const FSubdivideParameters& InParameters, EDisplaceMeshToolSubdivisionType InSubdivisionType); void CalculateResult(FProgressCancel* Progress) final; TUniquePtr> ExtractVerticesToDisplace() { return MoveTemp(VerticesToDisplace); } protected: TUniquePtr> 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 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>(); 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 Pattern =FSelectiveTessellate::CreateConcentricRingsTessellationPattern(ResultMesh.Get(), SubdivisionsCount, Parameters.SelectedTriangles); FDynamicMesh3 OutMesh; FSelectiveTessellate Tessellator(ResultMesh.Get(), &OutMesh); Tessellator.Progress = ProgressCancel; Tessellator.SetPattern(Pattern.Get()); VerticesToDisplace = MakeUnique>(); 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 WeightMapIn); void SetSelectionType(EDisplaceMeshToolTriangleSelectionType SelectionTypeIn) { Parameters.SelectionType = SelectionTypeIn; } // Materials void SetActiveMaterialID(int ActiveMaterialID) { Parameters.ActiveMaterialID = ActiveMaterialID; } TUniquePtr MakeNewOperator() final { return MakeUnique(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 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 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 AdjustmentCurve; TSharedPtr WeightMap; TFunction WeightMapQueryFunc; TSharedPtr, ESPMode::ThreadSafe> VerticesToDisplace; // if set, only displace vertices whose ids are part of the array }; class FDisplaceMeshOp : public FDynamicMeshOperator { public: FDisplaceMeshOp(TSharedPtr SourceMeshIn, const DisplaceMeshParameters& DisplaceParametersIn, EDisplaceMeshToolDisplaceType DisplacementTypeIn); void CalculateResult(FProgressCancel* Progress) final; private: TSharedPtr SourceMesh; DisplaceMeshParameters Parameters; EDisplaceMeshToolDisplaceType DisplacementType; TArray SourcePositions; FMeshNormals SourceNormals; TArray DisplacedPositions; }; FDisplaceMeshOp::FDisplaceMeshOp(TSharedPtr 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 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& 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& PerlinLayerProperties); void SetWeightMap(TSharedPtr WeightMap); void SetRecalculateNormals(bool bRecalculateNormals); void SetVerticesToDisplace(TSharedPtr, ESPMode::ThreadSafe> VerticesToDisplace); TUniquePtr MakeNewOperator() final { return MakeUnique(SourceMesh, Parameters, DisplacementType); } private: void UpdateMap(); DisplaceMeshParameters Parameters; EDisplaceMeshToolDisplaceType DisplacementType; TSharedPtr& 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( static_cast(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 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& LayerProperties ) { Parameters.PerlinLayerProperties = LayerProperties; } void FDisplaceMeshOpFactory::SetWeightMap(TSharedPtr WeightMap) { Parameters.WeightMap = WeightMap; } void FDisplaceMeshOpFactory::SetRecalculateNormals(bool RecalcNormalsIn) { Parameters.bRecalculateNormals = RecalcNormalsIn; } void FDisplaceMeshOpFactory::SetVerticesToDisplace(TSharedPtr, ESPMode::ThreadSafe> VerticesToDisplace) { Parameters.VerticesToDisplace = VerticesToDisplace; } } // namespace /* * ToolBuilder */ USingleTargetWithSelectionTool* UDisplaceMeshToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { return NewObject(SceneState.ToolManager); } const FToolTargetTypeRequirements& UDisplaceMeshToolBuilder::GetTargetRequirements() const { static FToolTargetTypeRequirements TypeRequirements({ UMaterialProvider::StaticClass(), UMeshDescriptionProvider::StaticClass(), UMeshDescriptionCommitter::StaticClass(), UPrimitiveComponentBackedTarget::StaticClass() }); return TypeRequirements; } /* * Tool */ TArray UDisplaceMeshCommonProperties::GetWeightMapsFunc() { return WeightMapsList; } void UDisplaceMeshTool::Setup() { using namespace DisplaceMeshToolLocals; UInteractiveTool::Setup(); // UInteractiveToolPropertySets NoiseProperties = NewObject(); NoiseProperties->RestoreProperties(this); CommonProperties = NewObject(); CommonProperties->RestoreProperties(this); DirectionalFilterProperties = NewObject(); DirectionalFilterProperties->RestoreProperties(this); TextureMapProperties = NewObject(); TextureMapProperties->RestoreProperties(this); SineWaveProperties = NewObject(); SineWaveProperties->RestoreProperties(this); SelectiveTessellationProperties = NewObject(); 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 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(FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo); DynamicMeshComponent = NewObject(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 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(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(OriginalMesh, SubParameters, CommonProperties->SubdivisionType); MeshStatistics = NewObject(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(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(Displacer.Get()); FSubdivideMeshOpFactory* SubdividerDownCast = static_cast(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(TextureMapProperties->Channel)); } else if (PropName == GET_MEMBER_NAME_CHECKED(UDisplaceMeshTextureMapProperties, Channel)) { DisplacerDownCast->SetDisplacementMap(TextureMapProperties->DisplacementMap, static_cast(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(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(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>(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 SubOp = SubdivideTask->GetTask().ExtractOperator(); FSubdivideMeshOp* SubOpDownCast = static_cast(SubOp.Get()); SubdividedMesh = TSharedPtr(SubOp->ExtractResult().Release()); VerticesToDisplace = TSharedPtr, ESPMode::ThreadSafe>(SubOpDownCast->ExtractVerticesToDisplace().Release()); MeshStatistics->Update(*SubdividedMesh); delete SubdivideTask; SubdivideTask = nullptr; } if (SubdividedMesh && bNeedsDisplaced) { // force update of contrast curve FDisplaceMeshOpFactory* DisplacerDownCast = static_cast(Displacer.Get()); DisplacerDownCast->SetAdjustmentCurve(TextureMapProperties->bApplyAdjustmentCurve ? TextureMapProperties->AdjustmentCurve : nullptr); DisplacerDownCast->SetVerticesToDisplace(VerticesToDisplace); DisplaceTask = new FAsyncTaskExecuterWithAbort>(Displacer->MakeNewOperator()); DisplaceTask->StartBackgroundTask(); bNeedsDisplaced = false; } if (DisplaceTask && DisplaceTask->IsDone()) { TUniquePtr 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 NewWeightMap = MakeShared(); 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::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)