Files
UnrealEngine/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/Operations/TransferDynamicMeshAttributes.cpp
2025-05-18 13:04:45 +08:00

791 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Operations/TransferDynamicMeshAttributes.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/DynamicMeshAABBTree3.h"
#include "DynamicMesh/MeshNormals.h"
#include "Util/ProgressCancel.h"
#include "Async/ParallelFor.h"
#include "TransformTypes.h"
#include "Algo/Count.h"
#include "Solvers/Internal/QuadraticProgramming.h"
#include "Solvers/LaplacianMatrixAssembly.h"
#include "Operations/SmoothDynamicMeshAttributes.h"
using namespace UE::Geometry;
namespace TransferValuesLocals
{
static constexpr int32 NumElements = 4;
/**
* Given a triangle and point on a triangle (via barycentric coordinates), compute the color.
*
* @param OutColor Interpolated color for a vertex with Bary barycentric coordinates
* @param TriElements The vertices of a triangle containing the point we are interpolating the colors for
* @param Bary Barycentric coordinates of the point
* @param InColorAttribute Attribute containing colors of the mesh that TriVertices belong to
*/
void InterpolateVertexAttribute(FVector4f& OutColor,
const FIndex3i& TriElements,
const FVector3f& Bary,
const FDynamicMeshColorOverlay* InColorAttribute)
{
FVector4f Value1, Value2, Value3;
InColorAttribute->GetElement(TriElements[0], Value1);
InColorAttribute->GetElement(TriElements[1], Value2);
InColorAttribute->GetElement(TriElements[2], Value3);
const float Alpha = Bary[0], Beta = Bary[1], Theta = Bary[2];
for (int32 Idx = 0; Idx < NumElements; ++Idx)
{
OutColor[Idx] = Alpha * Value1[Idx] + Beta * Value2[Idx] + Theta * Value3[Idx];
}
}
FDynamicMeshColorOverlay* GetOrCreateColorAttribute(FDynamicMesh3& InMesh, const bool InSplit)
{
checkSlow(InMesh.HasAttributes());
FDynamicMeshAttributeSet* MeshAttributes = InMesh.Attributes();
if (!MeshAttributes->HasPrimaryColors())
{
MeshAttributes->EnablePrimaryColors();
// Start with a clean attribute and elements we can write to.
MeshAttributes->PrimaryColors()->CreateFromPredicate([](int /*ParentVID*/, int /*TriIDA*/, int /*TriIDB*/){return true;}, 0.f);
}
if (InSplit)
{
// create vertex instances for each face if needed
FDynamicMeshColorOverlay* ColorOverlay = MeshAttributes->PrimaryColors();
ColorOverlay->SplitVerticesWithPredicate(
[](int ElementIdx, int TriID) { return true; },
[ColorOverlay](int ElementIdx, int TriID, float* FillVect)
{
const FVector4f CurValue = ColorOverlay->GetElement(ElementIdx);
FillVect[0] = CurValue.X; FillVect[1] = CurValue.Y; FillVect[2] = CurValue.Z; FillVect[3] = CurValue.W;
});
}
return MeshAttributes->PrimaryColors();
}
static FVector3f ToUENormal(const FVector3d& Normal)
{
return FVector3f((float)Normal.X, (float)Normal.Y, (float)Normal.Z);
}
struct FTriangleData
{
FVector3d Normal;
FVector3d Centroid;
};
void GetBiasedElementPositions(const FDynamicMesh3& InOutTargetMesh, const float InRatio, TArray<FVector>& OutElementPositions)
{
const FDynamicMeshColorOverlay* ColorOverlay = InOutTargetMesh.Attributes()->PrimaryColors();
if (!ensure(ColorOverlay))
{
return;
}
// get normal + centroid of each face
TArray<FTriangleData> TrianglesData;
TrianglesData.Reserve(InOutTargetMesh.TriangleCount());
double DummyTriArea;
for (int TriIdx : InOutTargetMesh.TriangleIndicesItr())
{
FTriangleData Data;
InOutTargetMesh.GetTriInfo(TriIdx, Data.Normal, DummyTriArea, Data.Centroid);
TrianglesData.Emplace(MoveTemp(Data));
}
// store the triangle for each element
TArray<int32> ElementToTriangle;
ElementToTriangle.Init(INDEX_NONE, ColorOverlay->MaxElementID());
for (int TriIdx : InOutTargetMesh.TriangleIndicesItr())
{
const FIndex3i TriElements = ColorOverlay->GetTriangle(TriIdx);
ElementToTriangle[TriElements[0]] = TriIdx;
ElementToTriangle[TriElements[1]] = TriIdx;
ElementToTriangle[TriElements[2]] = TriIdx;
}
// clamp the bias between UE_SMALL_NUMBER and 1.0
const double Ratio = FMath::Clamp(static_cast<double>(FMath::Abs(InRatio)), UE_KINDA_SMALL_NUMBER, 1.f);
// compute biased vertex instance positions (per ElementID)
// note that MaxElementID() is used here instead of ElementCount()
OutElementPositions.Reset();
OutElementPositions.Init(FVector::Zero(), ColorOverlay->MaxElementID());
for (const int32 ElementID : ColorOverlay->ElementIndicesItr())
{
const int32 ParentVertex = ColorOverlay->GetParentVertex(ElementID);
// initialize with the parent vertex position
FVector& VertexPos = OutElementPositions[ElementID];
VertexPos = InOutTargetMesh.GetVertexRef(ParentVertex);
// get the face the element belongs to
const int32 TriangleIndex = ElementToTriangle[ElementID];
if (TriangleIndex != INDEX_NONE)
{
const FTriangleData& Triangle = TrianglesData[TriangleIndex];
// build triangle base with VertexPos - TriCentroid as the X axis and the triangle Normal as Z
const FVector& TriCentroid = Triangle.Centroid;
const FVector X = (VertexPos - TriCentroid).GetSafeNormal();
const FVector Y = Triangle.Normal.Cross(X);
const FQuat Rotation = FRotationMatrix::MakeFromXY(X, Y).ToQuat();
const FTransform TriTransform(Rotation, Triangle.Centroid);
// slightly move the vertex along the X axis to "shrink the triangle"
VertexPos = TriTransform.InverseTransformPosition(VertexPos);
VertexPos.X = VertexPos.X - (VertexPos.X * Ratio);
VertexPos = TriTransform.TransformPosition(VertexPos);
}
}
}
struct FTaskContext
{
TArray<int32> ElementIDs;
};
}
FTransferVertexColorAttribute::FTransferVertexColorAttribute(
const FDynamicMesh3* InSourceMesh,
const FDynamicMeshAABBTree3* InSourceBVH)
: SourceMesh(InSourceMesh)
, SourceBVH(InSourceBVH)
{
// If the BVH for the source mesh was not specified then create one
if (SourceBVH == nullptr)
{
InternalSourceBVH = MakeUnique<FDynamicMeshAABBTree3>(SourceMesh);
}
}
FTransferVertexColorAttribute::~FTransferVertexColorAttribute()
{}
bool FTransferVertexColorAttribute::Cancelled()
{
return (Progress == nullptr) ? false : Progress->Cancelled();
}
EOperationValidationResult FTransferVertexColorAttribute::Validate()
{
if (SourceMesh == nullptr)
{
return EOperationValidationResult::Failed_UnknownReason;
}
// Either BVH was passed by the caller or was created internally in the constructor
if (SourceBVH == nullptr && InternalSourceBVH.IsValid() == false)
{
return EOperationValidationResult::Failed_UnknownReason;
}
if (!SourceMesh->HasAttributes())
{
return EOperationValidationResult::Failed_UnknownReason;
}
if (!SourceMesh->Attributes()->HasPrimaryColors())
{
return EOperationValidationResult::Failed_UnknownReason;
}
return EOperationValidationResult::Ok;
}
bool FTransferVertexColorAttribute::TransferColorsToMesh(FDynamicMesh3& InOutTargetMesh)
{
using namespace TransferValuesLocals;
if (Validate() != EOperationValidationResult::Ok)
{
return false;
}
if (!InOutTargetMesh.HasAttributes())
{
InOutTargetMesh.EnableAttributes();
}
// If we need to compare normals, make sure both the target and the source meshes have per-vertex normals data
TUniquePtr<FMeshNormals> InternalTargetMeshNormals;
if (NormalThreshold >= 0)
{
if (!SourceMesh->HasVertexNormals() && !InternalSourceMeshNormals)
{
// only do this once for the source mesh in case of subsequent calls to the method
InternalSourceMeshNormals = MakeUnique<FMeshNormals>(SourceMesh);
InternalSourceMeshNormals->ComputeVertexNormals();
}
if (!InOutTargetMesh.HasVertexNormals())
{
InternalTargetMeshNormals = MakeUnique<FMeshNormals>(&InOutTargetMesh);
InternalTargetMeshNormals->ComputeVertexNormals();
}
}
FDynamicMeshColorOverlay* TargetColors = GetOrCreateColorAttribute(InOutTargetMesh, bHardEdges);
checkSlow(TargetColors);
bool bFailed = false;
// compute the transfer only for the subset of vertices if necessary
const bool bUseSubset = !TargetVerticesSubset.IsEmpty();
const int32 NumVerticesToTransfer = bUseSubset ? TargetVerticesSubset.Num() : InOutTargetMesh.MaxVertexID();
if (TransferMethod == ETransferMethod::ClosestPointOnSurface)
{
const int32 NumMatched = TransferUsingClosestPoint(InOutTargetMesh, InternalTargetMeshNormals);
const int32 NumVerticesToMatch = bHardEdges ? TargetColors->ElementCount() : NumVerticesToTransfer;
// If the caller requested to simply find the closest point for all vertices then the number of matched vertices
// must be equal to the target mesh vertex count
if (SearchRadius < 0 && NormalThreshold < 0)
{
bFailed = NumMatched != NumVerticesToMatch;
}
}
else if (TransferMethod == ETransferMethod::Inpaint)
{
/**
* Given two meshes, Mesh1 without colors and Mesh2 with colors, assume they are aligned in 3d space.
* For every vertex on Mesh1 find the closest point on the surface of Mesh2 within a radius R. If the difference
* between the normals of the two points is below the threshold, then it's a match. Otherwise no match.
* So now we have two sets of vertices on Mesh1. One with a match on the source mesh and one without a match.
* For all the vertices with a match, copy values over. For all the vertices without the match, do nothing.
* Now, for all the vertices without a match, try to approximate the values by smoothly interpolating between
* the values at the known vertices via solving a quadratic problem.
*
* The solver minimizes an energy
* trace(W^t Q W)
* W \in R^(nxm) is a matrix where n is the number of vertices and m is the number of elements.
* Q \in R^(nxn) is a matrix that combines both Dirichlet and Laplacian energies, Q = -L + L*M^(-1)*L
* where L is a cotangent Laplacian and M is a mass matrix
*
*/
MatchedVertices.Init(false, InOutTargetMesh.MaxVertexID());
TArray<FVector4f> MatchedColors;
MatchedColors.Init(FVector4f::Zero(), InOutTargetMesh.MaxVertexID());
// because the inpaint algorithm can extract data from regions outside the target vertex subset, a temporary attribute is used to modify the values.
// NOTE: make sure to copy the values of the vertex subset into the complete TargetColors attribute before exciting the function. (see CopySubsetColorsIfNeeded)
FDynamicMeshColorOverlay SubsetTargetColors;
if (bUseSubset)
{
SubsetTargetColors.Copy(*TargetColors);
}
FDynamicMeshColorOverlay* EditedColors = bUseSubset ? &SubsetTargetColors : TargetColors;
// Task context for the parallel for loops down below, to avoid repeatedly re-allocating an array.
TArray<FTaskContext> TaskContexts;
// For every vertex on the target mesh try to find the match on the source mesh using the distance and normal checks
ParallelForWithTaskContext(TaskContexts, InOutTargetMesh.MaxVertexID(), [this, &InOutTargetMesh, &EditedColors, &InternalTargetMeshNormals, &MatchedColors](FTaskContext& Context, int32 VertexID)
{
if (Cancelled())
{
return;
}
if (InOutTargetMesh.IsVertex(VertexID))
{
// check if we need to force the vertex to not have a match
if (ForceInpaint.Num() == InOutTargetMesh.MaxVertexID() && ForceInpaint[VertexID] != 0)
{
return;
}
const FVector3d Point = InOutTargetMesh.GetVertex(VertexID);
FVector3f Normal = FVector3f::UnitY();
if (NormalThreshold >= 0)
{
const bool bHasNormals = InOutTargetMesh.HasVertexNormals();
if (ensure(bHasNormals || InternalTargetMeshNormals.IsValid()))
{
Normal = bHasNormals ? InOutTargetMesh.GetVertexNormal(VertexID) : ToUENormal(InternalTargetMeshNormals->GetNormals()[VertexID]);
}
}
FVector4f& Color = MatchedColors[VertexID];
if (TransferColorToPoint(Color, Point, Normal))
{
EditedColors->GetVertexElements(VertexID, Context.ElementIDs);
for (int32 ElementID: Context.ElementIDs)
{
EditedColors->SetElement(ElementID, Color);
}
MatchedVertices[VertexID] = true;
}
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
if (Cancelled())
{
return false;
}
int32 NumMatched = 0;
if (!bUseSubset)
{
for (bool Flag : MatchedVertices)
{
if (Flag)
{
NumMatched++;
}
}
}
else
{
NumMatched = (int32)Algo::CountIf(TargetVerticesSubset, [this](int32 VertexID)
{
return MatchedVertices.IsValidIndex(VertexID) && MatchedVertices[VertexID];
});
}
// If no vertices matched, we have nothing to inpaint.
if (NumMatched == 0)
{
return false;
}
auto CopySubsetColorsIfNeeded = [Subset = TargetVerticesSubset, &EditedColors, &TargetColors, &InOutTargetMesh]()
{
TArray<int32> ElementIDs;
if (EditedColors && EditedColors != TargetColors)
{
for (const int32 VertexID: Subset)
{
if (InOutTargetMesh.IsVertex(VertexID))
{
TargetColors->GetVertexElements(VertexID, ElementIDs);
for (int32 ElementID: ElementIDs)
{
FVector4f Color;
EditedColors->GetElement(ElementID, Color);
TargetColors->SetElement(ElementID, Color);
}
}
}
}
};
// If all vertices were matched then nothing else to do
if (NumMatched == NumVerticesToTransfer)
{
// copy colors from the subset attribute if using subset
CopySubsetColorsIfNeeded();
return true;
}
// Compute linearization so we can store constraints at linearized indices
FVertexLinearization VtxLinearization(InOutTargetMesh, false);
const TArray<int32>& ToMeshV = VtxLinearization.ToId();
const TArray<int32>& ToIndex = VtxLinearization.ToIndex();
// Setup the sparse matrix FixedValues of known (matched) colors and the array (FixedIndices) of the matched vertex IDs
FSparseMatrixD FixedValues;
FixedValues.resize(NumMatched, NumElements);
std::vector<Eigen::Triplet<FSparseMatrixD::Scalar>> FixedValuesTriplets;
FixedValuesTriplets.reserve(NumMatched);
TArray<int> FixedIndices;
FixedIndices.Reserve(NumMatched);
for (int32 VertexID = 0; VertexID < InOutTargetMesh.MaxVertexID(); ++VertexID)
{
if (InOutTargetMesh.IsVertex(VertexID) && MatchedVertices[VertexID])
{
const FVector4f& Color = MatchedColors[VertexID];
const int32 CurIdx = FixedIndices.Num();
for (int32 Idx = 0; Idx < NumElements; ++Idx)
{
FixedValuesTriplets.emplace_back(CurIdx, Idx, Color[Idx]);
}
checkSlow(VertexID < ToIndex.Num());
FixedIndices.Add(ToIndex[VertexID]);
}
}
FixedValues.setFromTriplets(FixedValuesTriplets.begin(), FixedValuesTriplets.end());
const int32 NumVerts = VtxLinearization.NumVerts();
FEigenSparseMatrixAssembler CotangentAssembler(NumVerts, NumVerts);
FEigenSparseMatrixAssembler LaplacianAssembler(NumVerts, NumVerts);
if (bUseIntrinsicLaplacian)
{
// Construct the Cotangent values matrix
UE::MeshDeformation::ConstructFullIDTCotangentLaplacian<double>(InOutTargetMesh, VtxLinearization, CotangentAssembler,
UE::MeshDeformation::ECotangentWeightMode::Default,
UE::MeshDeformation::ECotangentAreaMode::NoArea);
// Construct the Laplacian with cotangent values scaled by the voronoi area (i.e. M^(-1)*L matrix where M is the mass/stiffness matrix)
UE::MeshDeformation::ConstructFullIDTCotangentLaplacian<double>(InOutTargetMesh, VtxLinearization, LaplacianAssembler,
UE::MeshDeformation::ECotangentWeightMode::Default,
UE::MeshDeformation::ECotangentAreaMode::VoronoiArea);
}
else
{
UE::MeshDeformation::ConstructFullCotangentLaplacian<double>(InOutTargetMesh, VtxLinearization, CotangentAssembler,
UE::MeshDeformation::ECotangentWeightMode::Default,
UE::MeshDeformation::ECotangentAreaMode::NoArea);
UE::MeshDeformation::ConstructFullCotangentLaplacian<double>(InOutTargetMesh, VtxLinearization, LaplacianAssembler,
UE::MeshDeformation::ECotangentWeightMode::Default,
UE::MeshDeformation::ECotangentAreaMode::VoronoiArea);
}
FSparseMatrixD CotangentMatrix, MassCotangentMatrix;
CotangentAssembler.ExtractResult(CotangentMatrix);
LaplacianAssembler.ExtractResult(MassCotangentMatrix);
// -L * L* M^(-1)*L energy
FSparseMatrixD Energy = -1*CotangentMatrix + CotangentMatrix*MassCotangentMatrix;
// Solve the QP problem with fixed constraints
FSparseMatrixD TargetValues;
TArray<int> VariableRows;
// We want the solution TargetValues matrix to only contain the rows representing the variable (non-fixed) rows
constexpr bool bVariablesOnly = true;
bFailed = !FQuadraticProgramming::SolveWithFixedConstraints(Energy, nullptr, FixedIndices, FixedValues, TargetValues, bVariablesOnly, KINDA_SMALL_NUMBER, &VariableRows);
checkSlow((VariableRows.Num() + FixedIndices.Num()) == Energy.rows());
if (!bFailed)
{
// Transpose so we can efficiently iterate over the col-major matrix. Each column now contains per-vertex values.
// Otherwise, we are iterating over rows of a col-major matrix which is slow.
FSparseMatrixD TargetValuesTransposed = TargetValues.transpose();
// Iterate over every column containing all values for the vertex
TArray<int32> ElementIDs;
for (int32 ColIdx = 0; ColIdx < TargetValuesTransposed.outerSize(); ++ColIdx)
{
FVector4f Data(0.f);
FInt32Vector4 N(0);
// Iterate over only non-zero rows (i.e. non-zero values)
for (FSparseMatrixD::InnerIterator Itr(TargetValuesTransposed, ColIdx); Itr; ++Itr)
{
const int32 Index = static_cast<int32>(Itr.row());
const float Value = static_cast<float>(Itr.value());
Data[Index] += Value;
N[Index]++;
}
// normalize
for (int32 Index = 0; Index < NumElements; ++Index)
{
if (N[Index] > 1)
{
Data[Index] /= static_cast<float>(N[Index]);
}
}
const int32 VertexIDLinearalized = bVariablesOnly ? static_cast<int32>(VariableRows[ColIdx]) : ColIdx; // linearized vertex ID (matrix row) of the variable in the Energy matrix
const int32 VertexID = ToMeshV[VertexIDLinearalized];
EditedColors->GetVertexElements(VertexID, ElementIDs);
for (int32 ElementID: ElementIDs)
{
EditedColors->SetElement(ElementID, Data);
}
}
// copy values from the subset attribute if using subset
CopySubsetColorsIfNeeded();
// Optional post-processing smoothing of the values at the vertices without a match
if (NumSmoothingIterations > 0 && SmoothingStrength > 0)
{
TArray<int32> VerticesToSmooth;
const int32 NumNotMatched = InOutTargetMesh.VertexCount() - NumMatched;
VerticesToSmooth.Reserve(NumNotMatched);
for (int32 VertexID = 0; VertexID < InOutTargetMesh.MaxVertexID(); ++VertexID)
{
if (InOutTargetMesh.IsVertex(VertexID) && !MatchedVertices[VertexID])
{
VerticesToSmooth.Add(VertexID);
}
}
FSmoothDynamicMeshAttributes BlurOp(InOutTargetMesh);
BlurOp.NumIterations = NumSmoothingIterations;
BlurOp.Strength = SmoothingStrength;
BlurOp.EdgeWeightMethod = FSmoothDynamicMeshAttributes::EEdgeWeights::CotanWeights; //expose as param
BlurOp.Selection = MoveTemp(VerticesToSmooth);
TArray<bool> ValuesToSmooth;
ValuesToSmooth.Init(true, NumElements);
ensure( BlurOp.SmoothOverlay(TargetColors, ValuesToSmooth) );
}
}
}
else
{
checkNoEntry(); // unsupported method
}
if (Cancelled() || bFailed)
{
return false;
}
return true;
}
bool FTransferVertexColorAttribute::TransferColorToPoint(FVector4f& OutColor, const FVector3d& InPoint, const FVector3f& InNormal) const
{
using namespace TransferValuesLocals;
// Find the containing triangle and the barycentric coordinates of the closest point
int32 TriID;
FVector3d Bary;
if (!FindClosestPointOnSourceSurface(InPoint, TargetToWorld, TriID, Bary))
{
return false;
}
const FVector3f BaryF((float)Bary[0], (float)Bary[1], (float)Bary[2]);
const FDynamicMeshColorOverlay* SourceColors = SourceMesh->Attributes()->PrimaryColors();
const FIndex3i ColorTriElements = SourceColors->GetTriangle(TriID);
if (SearchRadius < 0 && NormalThreshold < 0)
{
// If the radius and normals are ignored, simply interpolate the values and return the result
InterpolateVertexAttribute(OutColor, ColorTriElements, BaryF, SourceColors);
}
else
{
bool bPassedRadiusCheck = true;
if (SearchRadius >= 0)
{
const FVector3d MatchedPoint = SourceMesh->GetTriBaryPoint(TriID, Bary[0], Bary[1], Bary[2]);
bPassedRadiusCheck = (InPoint - MatchedPoint).Length() <= SearchRadius;
}
bool bPassedNormalsCheck = true;
if (NormalThreshold >= 0)
{
FVector3f Normal0 = FVector3f::UnitY();
FVector3f Normal1 = FVector3f::UnitY();
FVector3f Normal2 = FVector3f::UnitY();
const bool bHasSourceNormals = SourceMesh->HasVertexNormals();
if (ensure(bHasSourceNormals || InternalSourceMeshNormals.IsValid()))
{
const FIndex3i TriVertices = SourceMesh->GetTriangle(TriID);
Normal0 = bHasSourceNormals ? SourceMesh->GetVertexNormal(TriVertices[0]) : ToUENormal(InternalSourceMeshNormals->GetNormals()[TriVertices[0]]);
Normal1 = bHasSourceNormals ? SourceMesh->GetVertexNormal(TriVertices[1]) : ToUENormal(InternalSourceMeshNormals->GetNormals()[TriVertices[1]]);
Normal2 = bHasSourceNormals ? SourceMesh->GetVertexNormal(TriVertices[2]) : ToUENormal(InternalSourceMeshNormals->GetNormals()[TriVertices[2]]);
}
const FVector3f MatchedNormal = Normalized(BaryF[0]*Normal0 + BaryF[1]*Normal1 + BaryF[2]*Normal2);
const FVector3f InNormalNormalized = Normalized(InNormal);
const float NormalAngle = FMathf::ACos(InNormalNormalized.Dot(MatchedNormal));
bPassedNormalsCheck = (double)NormalAngle <= NormalThreshold;
if (!bPassedNormalsCheck && LayeredMeshSupport)
{
// try again with a flipped normal
bPassedNormalsCheck = (double)(TMathUtil<float>::Pi - NormalAngle) <= NormalThreshold;
}
}
if (bPassedRadiusCheck && bPassedNormalsCheck)
{
InterpolateVertexAttribute(OutColor, ColorTriElements, BaryF, SourceColors);
}
else
{
return false;
}
}
return true;
}
bool FTransferVertexColorAttribute::FindClosestPointOnSourceSurface(const FVector3d& InPoint, const FTransformSRT3d& InToWorld, int32& NearTriID, FVector3d& Bary) const
{
IMeshSpatial::FQueryOptions Options;
double NearestDistSqr;
const FVector3d WorldPoint = InToWorld.TransformPosition(InPoint);
if (SourceBVH != nullptr)
{
NearTriID = SourceBVH->FindNearestTriangle(WorldPoint, NearestDistSqr, Options);
}
else
{
NearTriID = InternalSourceBVH->FindNearestTriangle(WorldPoint, NearestDistSqr, Options);
}
if (!ensure(NearTriID != IndexConstants::InvalidID))
{
return false;
}
const FDistPoint3Triangle3d Query = TMeshQueries<FDynamicMesh3>::TriangleDistance(*SourceMesh, NearTriID, WorldPoint);
const FVector3d NearestPnt = Query.ClosestTrianglePoint;
const FIndex3i TriVertex = SourceMesh->GetTriangle(NearTriID);
Bary = VectorUtil::BarycentricCoords(NearestPnt, SourceMesh->GetVertexRef(TriVertex.A),
SourceMesh->GetVertexRef(TriVertex.B),
SourceMesh->GetVertexRef(TriVertex.C));
return true;
}
int32 FTransferVertexColorAttribute::TransferUsingClosestPoint(FDynamicMesh3& InOutTargetMesh, const TUniquePtr<FMeshNormals>& InTargetMeshNormals)
{
using namespace TransferValuesLocals;
if (!ensure(TransferMethod == ETransferMethod::ClosestPointOnSurface))
{
return 0;
}
FDynamicMeshAttributeSet* MeshAttributes = InOutTargetMesh.Attributes();
if (!ensure(MeshAttributes->HasPrimaryColors()))
{
return 0;
}
FDynamicMeshColorOverlay* TargetColors = MeshAttributes->PrimaryColors();
// transfer using element ids instead of vertices
if (bHardEdges)
{
// compute per-vertex instance biased positions
TArray<FVector> BiasedPositions;
GetBiasedElementPositions(InOutTargetMesh, BiasRatio, BiasedPositions);
// note that MaxElementID() is used here instead of ElementCount()
const int32 NumElementsToTransfer = TargetColors->MaxElementID();
MatchedVertices.Init(false, NumElementsToTransfer);
ParallelFor(NumElementsToTransfer, [this, &InOutTargetMesh, &TargetColors, &InTargetMeshNormals, &BiasedPositions](int32 InElementID)
{
if (Cancelled())
{
return;
}
if (TargetColors->IsElement(InElementID))
{
const int32 VertexID = TargetColors->GetParentVertex(InElementID);
if (InOutTargetMesh.IsVertex(VertexID))
{
const FVector3d& BiasedPoint = BiasedPositions.IsValidIndex(InElementID) ? BiasedPositions[InElementID] : InOutTargetMesh.GetVertexRef(VertexID);
FVector3f Normal = FVector3f::UnitY();
if (NormalThreshold >= 0)
{
const bool bHasNormals = InOutTargetMesh.HasVertexNormals();
if (ensure(bHasNormals || InTargetMeshNormals))
{
Normal = bHasNormals ? InOutTargetMesh.GetVertexNormal(VertexID) : ToUENormal(InTargetMeshNormals->GetNormals()[VertexID]);
}
}
FVector4f Color;
if (TransferColorToPoint(Color, BiasedPoint, Normal))
{
TargetColors->SetElement(InElementID, Color);
MatchedVertices[InElementID] = true;
}
}
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
// compute matching number
int32 NumMatched = 0;
for (const int32 ElementID : TargetColors->ElementIndicesItr())
{
if (MatchedVertices[ElementID])
{
NumMatched++;
}
}
return NumMatched;
}
// compute the transfer only for the subset of vertices if necessary
const bool bUseSubset = !TargetVerticesSubset.IsEmpty();
const int32 NumVerticesToTransfer = bUseSubset ? TargetVerticesSubset.Num() : InOutTargetMesh.MaxVertexID();
TArray<FTaskContext> TaskContexts;
MatchedVertices.Init(false, NumVerticesToTransfer);
ParallelForWithTaskContext(TaskContexts, NumVerticesToTransfer, [this, &InOutTargetMesh, &TargetColors, &InTargetMeshNormals, bUseSubset](FTaskContext& Context, int32 InVertexID)
{
if (Cancelled())
{
return;
}
const int32 VertexID = bUseSubset ? TargetVerticesSubset[InVertexID] : InVertexID;
if (InOutTargetMesh.IsVertex(VertexID))
{
const FVector3d Point = InOutTargetMesh.GetVertex(VertexID);
FVector3f Normal = FVector3f::UnitY();
if (NormalThreshold >= 0)
{
const bool bHasNormals = InOutTargetMesh.HasVertexNormals();
if (ensure(bHasNormals || InTargetMeshNormals))
{
Normal = bHasNormals ? InOutTargetMesh.GetVertexNormal(VertexID) : ToUENormal(InTargetMeshNormals->GetNormals()[VertexID]);
}
}
FVector4f Color;
if (TransferColorToPoint(Color, Point, Normal))
{
TargetColors->GetVertexElements(VertexID, Context.ElementIDs);
for (int32 ElementID: Context.ElementIDs)
{
TargetColors->SetElement(ElementID, Color);
}
MatchedVertices[VertexID] = true;
}
}
}, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
int32 NumMatched = 0;
for (bool Flag : MatchedVertices)
{
if (Flag)
{
NumMatched++;
}
}
return NumMatched;
}