Files
2025-05-18 13:04:45 +08:00

1610 lines
60 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Dataflow/GeometryCollectionFracturingNodes.h"
#include "Dataflow/DataflowCore.h"
#if WITH_EDITOR
#include "Dataflow/DataflowRenderingViewMode.h"
#endif
#include "Engine/StaticMesh.h"
#include "GeometryCollection/GeometryCollectionObject.h"
#include "GeometryCollection/ManagedArrayCollection.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionEngineUtility.h"
#include "GeometryCollection/GeometryCollectionEngineConversion.h"
#include "Logging/LogMacros.h"
#include "Templates/SharedPointer.h"
#include "UObject/UnrealTypePrivate.h"
#include "DynamicMeshToMeshDescription.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "StaticMeshAttributes.h"
#include "DynamicMeshEditor.h"
#include "Operations/MeshBoolean.h"
#include "EngineGlobals.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "GeometryCollection/GeometryCollectionConvexUtility.h"
#include "Voronoi/Voronoi.h"
#include "PlanarCut.h"
#include "GeometryCollection/GeometryCollectionProximityUtility.h"
#include "FractureEngineClustering.h"
#include "FractureEngineSelection.h"
#include "GeometryCollection/Facades/CollectionTransformSelectionFacade.h"
#include "GeometryCollection/Facades/CollectionBoundsFacade.h"
#include "Dataflow/DataflowSelection.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "UDynamicMesh.h"
#include "MeshDescription.h"
#include "DynamicMeshToMeshDescription.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "StaticMeshAttributes.h"
#include "Dataflow/DataflowSimpleDebugDrawMesh.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GeometryCollectionFracturingNodes)
namespace UE::Dataflow
{
void GeometryCollectionFracturingNodes()
{
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FUniformScatterPointsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FUniformScatterPointsDataflowNode_v2);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FRadialScatterPointsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FRadialScatterPointsDataflowNode_v2);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FGridScatterPointsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FClusterScatterPointsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FVoronoiFractureDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FVoronoiFractureDataflowNode_v2);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FPlaneCutterDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FPlaneCutterDataflowNode_v2);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FExplodedViewDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FSliceCutterDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FBrickCutterDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMeshCutterDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FUniformFractureDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FVisualizeFractureDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FTransformPointsDataflowNode)
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FAppendPointsDataflowNode)
// Commented out until we decide how to make generic data setter nodes
// DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FSetFloatAttributeDataflowNode);
}
}
FClusterScatterPointsDataflowNode::FClusterScatterPointsDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&BoundingBox);
RegisterInputConnection(&NumberClustersMin).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&NumberClustersMax).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&PointsPerClusterMin).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&PointsPerClusterMax).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&ClusterRadiusFractionMin).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&ClusterRadiusFractionMax).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&ClusterRadiusOffset).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&RandomSeed).SetCanHidePin(true).SetPinIsHidden(true);
RegisterOutputConnection(&Points);
}
void FClusterScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Points))
{
const FBox& Bounds = GetValue<FBox>(Context, &BoundingBox);
if (Bounds.GetVolume() > 0.f)
{
FRandomStream RandStream(GetValue(Context, &RandomSeed));
const int32 InNumberClustersMin = FMath::Max(1, GetValue(Context, &NumberClustersMin));
const int32 InNumberClustersMax = FMath::Max(InNumberClustersMin, GetValue(Context, &NumberClustersMax));
const int32 ClusterCount = RandStream.RandRange(InNumberClustersMin, InNumberClustersMax);
const FVector Extent(Bounds.Max - Bounds.Min);
TArray<FVector> ClusterCenters;
ClusterCenters.Reserve(ClusterCount);
for (int32 ClusterIdx = 0; ClusterIdx < ClusterCount; ++ClusterIdx)
{
ClusterCenters.Emplace(Bounds.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
}
const int32 InPointsPerClusterMin = FMath::Max(0, GetValue(Context, &PointsPerClusterMin));
const int32 InPointsPerClusterMax = FMath::Max(InPointsPerClusterMin, GetValue(Context, &PointsPerClusterMax));
const double InClusterRadiusOffset = (double)GetValue(Context, &ClusterRadiusOffset);
const double InClusterRadiusFractionMin = FMath::Max(0, (double)GetValue(Context, &ClusterRadiusFractionMin));
const double InClusterRadiusFractionMax = FMath::Max(InClusterRadiusFractionMin, (double)GetValue(Context, &ClusterRadiusFractionMax));
const double BoundsSize = Bounds.GetExtent().GetAbsMax();
TArray<FVector> NewPoints;
NewPoints.Reserve(ClusterCount * FMath::CeilToInt32(double(InPointsPerClusterMin + InPointsPerClusterMax) * .5));
for (int32 CenterIdx = 0; CenterIdx < ClusterCenters.Num(); ++CenterIdx)
{
const int32 SubPointCount = RandStream.RandRange(InPointsPerClusterMin, InPointsPerClusterMax);
for (int32 SubPointIdx = 0; SubPointIdx < SubPointCount; ++SubPointIdx)
{
FVector V(RandStream.VRand());
V.Normalize();
V *= InClusterRadiusOffset + (RandStream.FRandRange(InClusterRadiusFractionMin, InClusterRadiusFractionMax) * BoundsSize);
V += ClusterCenters[CenterIdx];
NewPoints.Emplace(V);
}
}
SetValue(Context, MoveTemp(NewPoints), &Points);
}
else
{
// ERROR: Invalid BoundingBox input
SetValue(Context, TArray<FVector>(), &Points);
}
}
}
void FUniformScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TArray<FVector>>(&Points))
{
const FBox& BBox = GetValue<FBox>(Context, &BoundingBox);
if (BBox.GetVolume() > 0.f)
{
FRandomStream RandStream(GetValue<float>(Context, &RandomSeed));
const FVector Extent(BBox.Max - BBox.Min);
const int32 NumPoints = RandStream.RandRange(GetValue<int32>(Context, &MinNumberOfPoints), GetValue<int32>(Context, &MaxNumberOfPoints));
TArray<FVector> PointsArr;
PointsArr.Reserve(NumPoints);
for (int32 Idx = 0; Idx < NumPoints; ++Idx)
{
PointsArr.Emplace(BBox.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
}
SetValue(Context, MoveTemp(PointsArr), &Points);
}
else
{
// ERROR: Invalid BoundingBox input
SetValue(Context, TArray<FVector>(), &Points);
}
}
}
void FUniformScatterPointsDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TArray<FVector>>(&Points))
{
const FBox& BBox = GetValue<FBox>(Context, &BoundingBox);
if (BBox.GetVolume() > 0.f)
{
FRandomStream RandStream(GetValue<int32>(Context, &RandomSeed));
const FVector Extent(BBox.Max - BBox.Min);
const int32 NumPoints = RandStream.RandRange(GetValue<int32>(Context, &MinNumberOfPoints), GetValue<int32>(Context, &MaxNumberOfPoints));
TArray<FVector> PointsArr;
PointsArr.Reserve(NumPoints);
for (int32 Idx = 0; Idx < NumPoints; ++Idx)
{
PointsArr.Emplace(BBox.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
}
SetValue(Context, MoveTemp(PointsArr), &Points);
}
else
{
// ERROR: Invalid BoundingBox input
SetValue(Context, TArray<FVector>(), &Points);
}
}
}
void FRadialScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TArray<FVector>>(&Points))
{
const FVector::FReal RadialStep = GetValue<float>(Context, &Radius) / GetValue<int32>(Context, &RadialSteps);
const FVector::FReal AngularStep = 2 * PI / GetValue<int32>(Context, &AngularSteps);
FRandomStream RandStream(GetValue<float>(Context, &RandomSeed));
FVector UpVector(GetValue<FVector>(Context, &Normal));
UpVector.Normalize();
FVector BasisX, BasisY;
UpVector.FindBestAxisVectors(BasisX, BasisY);
TArray<FVector> PointsArr;
FVector::FReal Len = RadialStep * .5;
for (int32 ii = 0; ii < GetValue<int32>(Context, &RadialSteps); ++ii, Len += RadialStep)
{
FVector::FReal Angle = FMath::DegreesToRadians(GetValue<float>(Context, &AngleOffset));
for (int32 kk = 0; kk < AngularSteps; ++kk, Angle += AngularStep)
{
FVector RotatingOffset = Len * (FMath::Cos(Angle) * BasisX + FMath::Sin(Angle) * BasisY);
PointsArr.Emplace(GetValue<FVector>(Context, &Center) + RotatingOffset + (RandStream.VRand() * RandStream.FRand() * Variability));
}
}
SetValue(Context, MoveTemp(PointsArr), &Points);
}
}
void FRadialScatterPointsDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TArray<FVector>>(&Points))
{
const FBox InBoundingBox = GetValue<FBox>(Context, &BoundingBox);
const FVector InCenter = GetValue<FVector>(Context, &Center);
const FVector InNormal = GetValue<FVector>(Context, &Normal);
const int32 InRandomSeed = GetValue<int32>(Context, &RandomSeed);
const int32 InAngularSteps = GetValue<int32>(Context, &AngularSteps);
const float InAngleOffset = GetValue<float>(Context, &AngleOffset);
const float InAngularNoise = GetValue<float>(Context, &AngularNoise);
const float InRadius = GetValue<float>(Context, &Radius);
const int32 InRadialSteps = GetValue<int32>(Context, &RadialSteps);
const float InRadialStepExponent = GetValue<float>(Context, &RadialStepExponent);
const float InRadialMinStep = GetValue<float>(Context, &RadialMinStep);
const float InRadialNoise = GetValue<float>(Context, &RadialNoise);
const float InRadialVariability = GetValue<float>(Context, &RadialVariability);
const float InAngularVariability = GetValue<float>(Context, &AngularVariability);
const float InAxialVariability = GetValue<float>(Context, &AxialVariability);
TArray<FVector> PointsArr;
const FVector::FReal AngularStep = 2 * PI / InAngularSteps;
FVector CenterVal(InBoundingBox.GetCenter() + InCenter);
FRandomStream RandStream(InRandomSeed);
FVector UpVector(InNormal);
UpVector.Normalize();
FVector BasisX, BasisY;
UpVector.FindBestAxisVectors(BasisX, BasisY);
// Precompute consistent noise for each angular step
TArray<FVector::FReal> AngleStepOffsets;
AngleStepOffsets.SetNumUninitialized(InAngularSteps);
for (int32 AngleIdx = 0; AngleIdx < InAngularSteps; ++AngleIdx)
{
AngleStepOffsets[AngleIdx] = FMath::DegreesToRadians(RandStream.FRandRange(-1, 1) * InAngularNoise);
}
// Compute radial positions following an (idx+1)^exp curve, and then re-normalize back to the Radius range
TArray<FVector::FReal> RadialPositions;
RadialPositions.SetNumUninitialized(InRadialSteps);
FVector::FReal StepOffset = 0;
for (int32 RadIdx = 0; RadIdx < InRadialSteps; ++RadIdx)
{
FVector::FReal RadialPos = FMath::Pow(RadIdx + 1, InRadialStepExponent) + StepOffset;
if (RadIdx == 0)
{
// Note we bring the first point a half-step toward the center, and shift all subsequent points accordingly
// so that for Exponent==1, the step from center to first boundary is the same distance as the step between each boundary
// (this is only necessary because there is no Voronoi site at the center)
RadialPos *= .5;
StepOffset = -RadialPos;
}
RadialPositions[RadIdx] = RadialPos;
}
// Normalize positions so that the diagram fits in the target radius
FVector::FReal RadialPosNorm = InRadius / RadialPositions.Last();
for (FVector::FReal& RadialPos : RadialPositions)
{
RadialPos = RadialPos * RadialPosNorm;
}
// Add radial noise
for (int32 RadIdx = 0; RadIdx < InRadialSteps; ++RadIdx)
{
FVector::FReal& RadialPos = RadialPositions[RadIdx];
// Offset by RadialNoise, but don't allow noise to take the value below 0
RadialPos += RandStream.FRandRange(-FMath::Min(RadialPos, InRadialNoise), InRadialNoise);
}
// make sure the positions remain in increasing order
RadialPositions.Sort();
// Adjust positions so they are never closer than the RadialMinStep
FVector::FReal LastRadialPos = 0;
for (int32 RadIdx = 0; RadIdx < InRadialSteps; ++RadIdx)
{
FVector::FReal MinStep = InRadialMinStep;
if (RadIdx == 0)
{
MinStep *= .5;
}
if (RadialPositions[RadIdx] - LastRadialPos < MinStep)
{
RadialPositions[RadIdx] = LastRadialPos + MinStep;
}
LastRadialPos = RadialPositions[RadIdx];
}
// Add a bit of noise to work around failure case in Voro++
// TODO: fix the failure case in Voro++ and remove this
float MinRadialVariability = InRadius > 1.f ? .0001f : 0.f;
float UseRadialVariability = FMath::Max(MinRadialVariability, InRadialVariability);
// Create the radial Voronoi sites
for (int32 ii = 0; ii < InRadialSteps; ++ii)
{
FVector::FReal Len = RadialPositions[ii];
FVector::FReal Angle = FMath::DegreesToRadians(InAngleOffset);
for (int32 kk = 0; kk < InAngularSteps; ++kk, Angle += AngularStep)
{
// Add the global noise and the per-point noise into the angle
FVector::FReal UseAngle = Angle + AngleStepOffsets[kk] + FMath::DegreesToRadians(RandStream.FRand() * InAngularVariability);
// Add per point noise into the radial position
FVector::FReal UseRadius = Len + FVector::FReal(RandStream.FRand() * UseRadialVariability);
FVector RotatingOffset = UseRadius * (FMath::Cos(UseAngle) * BasisX + FMath::Sin(UseAngle) * BasisY);
PointsArr.Emplace(CenterVal + RotatingOffset + UpVector * (RandStream.FRandRange(-1, 1) * InAxialVariability));
}
}
SetValue(Context, MoveTemp(PointsArr), &Points);
}
}
void FGridScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TArray<FVector>>(&Points))
{
const FBox& BBox = GetValue<FBox>(Context, &BoundingBox);
if (BBox.GetVolume() > 0.f)
{
const FVector Extent(BBox.Max - BBox.Min);
// Note: Should match ClampMax in the UI. Do not raise above 1290 to avoid overflowing the TArray.
// (A smaller limit is preferrable because the rendering / later processing will likely not want to handle that many points.)
constexpr int32 MaxPointsPerDim = 200;
const int32 NumPointsInX = FMath::Clamp(GetValue<int32>(Context, &NumberOfPointsInX), 0, MaxPointsPerDim);
const int32 NumPointsInY = FMath::Clamp(GetValue<int32>(Context, &NumberOfPointsInY), 0, MaxPointsPerDim);
const int32 NumPointsInZ = FMath::Clamp(GetValue<int32>(Context, &NumberOfPointsInZ), 0, MaxPointsPerDim);
if (NumPointsInX >= 1 && NumPointsInY >= 1 && NumPointsInZ >= 1)
{
const int32 NumPoints = NumPointsInX * NumPointsInY * NumPointsInZ;
const float dX = Extent.X / (float)NumPointsInX;
const float dY = Extent.Y / (float)NumPointsInY;
const float dZ = Extent.Z / (float)NumPointsInZ;
FRandomStream RandStream(GetValue<int32>(Context, &RandomSeed));
TArray<FVector> PointsArr;
PointsArr.Reserve(NumPoints);
for (int32 Idx_X = 0; Idx_X < NumPointsInX; ++Idx_X)
{
for (int32 Idx_Y = 0; Idx_Y < NumPointsInY; ++Idx_Y)
{
for (int32 Idx_Z = 0; Idx_Z < NumPointsInZ; ++Idx_Z)
{
FVector RandomDisplacement = FVector(RandStream.FRandRange(-1.f, 1.f) * GetValue<float>(Context, &MaxRandomDisplacementX),
RandStream.FRandRange(-1.f, 1.f) * GetValue<float>(Context, &MaxRandomDisplacementY),
RandStream.FRandRange(-1.f, 1.f) * GetValue<float>(Context, &MaxRandomDisplacementZ));
PointsArr.Emplace(BBox.Min.X + 0.5f * dX + (float)Idx_X * dX + RandomDisplacement.X,
BBox.Min.Y + 0.5f * dY + (float)Idx_Y * dY + RandomDisplacement.Y,
BBox.Min.Z + 0.5f * dZ + (float)Idx_Z * dZ + RandomDisplacement.Z);
}
}
}
SetValue(Context, MoveTemp(PointsArr), &Points);
}
else
{
// ERROR: Invalid number of points
SetValue(Context, TArray<FVector>(), &Points);
}
}
else
{
// ERROR: Invalid BoundingBox input
SetValue(Context, TArray<FVector>(), &Points);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////
FTransformPointsDataflowNode::FTransformPointsDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Points);
RegisterInputConnection(&Transform);
RegisterOutputConnection(&Points, &Points);
}
void FTransformPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Points))
{
const FTransform& InTransform = GetValue(Context, &Transform);
TArray<FVector> OutPoints = GetValue(Context, &Points);
for (FVector& Point : OutPoints)
{
Point = InTransform.TransformPosition(Point);
}
SetValue(Context, MoveTemp(OutPoints), &Points);
}
}
////////////////////////////////////////////////////////////////////////////////////////////
FAppendPointsDataflowNode::FAppendPointsDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&PointsA);
RegisterInputConnection(&PointsB);
RegisterOutputConnection(&Points, &PointsA);
}
void FAppendPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Points))
{
const TArray<FVector>& InPointsA = GetValue(Context, &PointsA);
const TArray<FVector>& InPointsB = GetValue(Context, &PointsB);
TArray<FVector> OutPoints;
OutPoints.Append(InPointsA);
OutPoints.Append(InPointsB);
SetValue(Context, MoveTemp(OutPoints), &Points);
}
}
////////////////////////////////////////////////////////////////////////////////////////////
void FVoronoiFractureDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection))
{
const FDataflowTransformSelection& InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
if (IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
FFractureEngineFracturing::VoronoiFracture(InCollection,
InTransformSelection,
GetValue<TArray<FVector>>(Context, &Points),
FTransform::Identity,
(int32)GetValue<float>(Context, &RandomSeed),
GetValue<float>(Context, &ChanceToFracture),
true,
GetValue<float>(Context, &Grout),
GetValue<float>(Context, &Amplitude),
GetValue<float>(Context, &Frequency),
GetValue<float>(Context, &Persistence),
GetValue<float>(Context, &Lacunarity),
GetValue<int32>(Context, &OctaveNumber),
GetValue<float>(Context, &PointSpacing),
AddSamplesForCollision,
GetValue<float>(Context, &CollisionSampleSpacing));
SetValue(Context, MoveTemp(InCollection), &Collection);
return;
}
}
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
SetValue(Context, InCollection, &Collection);
}
}
void FVoronoiFractureDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection) ||
Out->IsA<FDataflowTransformSelection>(&TransformSelection) ||
Out->IsA<FDataflowTransformSelection>(&NewGeometryTransformSelection))
{
FDataflowTransformSelection InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
int32 ResultGeometryIndex = FFractureEngineFracturing::VoronoiFracture(InCollection,
InTransformSelection,
GetValue<TArray<FVector>>(Context, &Points),
GetValue<FTransform>(Context, &Transform),
0, // RandomSeed is not used in Voronoi fracture, it is used in the source point generation
GetValue<float>(Context, &ChanceToFracture),
SplitIslands,
GetValue<float>(Context, &Grout),
GetValue<float>(Context, &Amplitude),
GetValue<float>(Context, &Frequency),
GetValue<float>(Context, &Persistence),
GetValue<float>(Context, &Lacunarity),
GetValue<int32>(Context, &OctaveNumber),
GetValue<float>(Context, &PointSpacing),
AddSamplesForCollision,
GetValue<float>(Context, &CollisionSampleSpacing));
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& GeometryToTransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
const int32 NumTransforms = InCollection.NumElements(FGeometryCollection::TransformGroup);
NewSelection.Initialize(NumTransforms, false);
OriginalSelection.Initialize(NumTransforms, false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 GeometryIdx = ResultGeometryIndex; GeometryIdx < GeometryToTransformIndices.Num(); ++GeometryIdx)
{
const int32 TransformIdx = GeometryToTransformIndices[GeometryIdx];
NewSelection.SetSelected(TransformIdx);
}
for (int32 TransformIdx = 0; TransformIdx < InTransformSelection.Num(); ++TransformIdx)
{
if (InTransformSelection.IsSelected(TransformIdx))
{
OriginalSelection.SetSelected(TransformIdx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
void FPlaneCutterDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection))
{
const FDataflowTransformSelection& InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
if (IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
FFractureEngineFracturing::PlaneCutter(InCollection,
InTransformSelection,
GetValue<FBox>(Context, &BoundingBox),
FTransform::Identity,
NumPlanes,
(int32)GetValue<float>(Context, &RandomSeed),
1.f,
true,
GetValue<float>(Context, &Grout),
GetValue<float>(Context, &Amplitude),
GetValue<float>(Context, &Frequency),
GetValue<float>(Context, &Persistence),
GetValue<float>(Context, &Lacunarity),
GetValue<int32>(Context, &OctaveNumber),
GetValue<float>(Context, &PointSpacing),
GetValue<bool>(Context, &AddSamplesForCollision),
GetValue<float>(Context, &CollisionSampleSpacing));
SetValue(Context, MoveTemp(InCollection), &Collection);
return;
}
}
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
SetValue(Context, InCollection, &Collection);
}
}
/*--------------------------------------------------------------------------------------------------------*/
static FLinearColor GetRandomColor(const int32 RandomSeed, int32 Idx)
{
FRandomStream RandomStream(RandomSeed * 23 + Idx * 4078);
const uint8 R = static_cast<uint8>(RandomStream.FRandRange(128, 255));
const uint8 G = static_cast<uint8>(RandomStream.FRandRange(128, 255));
const uint8 B = static_cast<uint8>(RandomStream.FRandRange(128, 255));
return FLinearColor(FColor(R, G, B, 255));
}
void FPlaneCutterDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection) ||
Out->IsA<FDataflowTransformSelection>(&TransformSelection) ||
Out->IsA<FDataflowTransformSelection>(&NewGeometryTransformSelection))
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
FBox InBoundingBox = GetValue(Context, &BoundingBox);
//
// If not connected get bounding box of incoming collection
//
if (!IsConnected(&BoundingBox))
{
GeometryCollection::Facades::FBoundsFacade BoundsFacade(InCollection);
const FBox& BoundingBoxInCollectionSpace = BoundsFacade.GetBoundingBoxInCollectionSpace();
InBoundingBox = BoundingBoxInCollectionSpace;
}
FDataflowTransformSelection InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
if (InTransformSelection.AnySelected())
{
int32 ResultGeometryIndex = FFractureEngineFracturing::PlaneCutter(InCollection,
InTransformSelection,
InBoundingBox,
GetValue(Context, &Transform),
NumPlanes,
GetValue(Context, &RandomSeed),
GetValue(Context, &ChanceToFracture),
SplitIslands,
GetValue(Context, &Grout),
GetValue(Context, &Amplitude),
GetValue(Context, &Frequency),
GetValue(Context, &Persistence),
GetValue(Context, &Lacunarity),
GetValue(Context, &OctaveNumber),
GetValue(Context, &PointSpacing),
AddSamplesForCollision,
GetValue(Context, &CollisionSampleSpacing));
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& TransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
NewSelection.Initialize(TransformIndices.Num(), false);
OriginalSelection.Initialize(TransformIndices.Num(), false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 Idx = ResultGeometryIndex; Idx < TransformIndices.Num(); ++Idx)
{
int32 BoneIdx = TransformIndices[Idx];
NewSelection.SetSelected(BoneIdx);
}
for (int32 Idx = 0; Idx < InTransformSelection.Num(); ++Idx)
{
if (InTransformSelection.IsSelected(Idx))
{
OriginalSelection.SetSelected(Idx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
#if WITH_EDITOR
bool FPlaneCutterDataflowNode_v2::CanDebugDrawViewMode(const FName& ViewModeName) const
{
return ViewModeName == UE::Dataflow::FDataflowConstruction3DViewMode::Name;
}
void FPlaneCutterDataflowNode_v2::DebugDraw(UE::Dataflow::FContext& Context, IDataflowDebugDrawInterface& DataflowRenderingInterface, const FDebugDrawParameters& DebugDrawParameters) const
{
if ((DebugDrawParameters.bNodeIsSelected || DebugDrawParameters.bNodeIsPinned))
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
FBox InBoundingBox = GetValue(Context, &BoundingBox);
if (!IsConnected(&BoundingBox))
{
GeometryCollection::Facades::FBoundsFacade BoundsFacade(InCollection);
const FBox& BoundingBoxInCollectionSpace = BoundsFacade.GetBoundingBoxInCollectionSpace();
InBoundingBox = BoundingBoxInCollectionSpace;
}
float PrincipalAxisLength = 2.f * FMath::Max3(InBoundingBox.GetExtent().X, InBoundingBox.GetExtent().Y, InBoundingBox.GetExtent().Z);
float PlaneSize = PrincipalAxisLength * PlaneSizeMultiplier;
const int32 InNumPlanes = GetValue(Context, &NumPlanes);
FNoiseSettings NoiseSettings;
NoiseSettings.Amplitude = GetValue(Context, &Amplitude);
NoiseSettings.Frequency = GetValue(Context, &Frequency);
NoiseSettings.Lacunarity = GetValue(Context, &Lacunarity);
NoiseSettings.Persistence = GetValue(Context, &Persistence);
NoiseSettings.Octaves = GetValue(Context, &OctaveNumber);
NoiseSettings.PointSpacing = GetValue(Context, &PointSpacing);
const int32 InRandomSeed = GetValue<int32>(Context, &RandomSeed);
TArray<FTransform> PlaneTransforms;
FFractureEngineFracturing::GenerateSliceTransforms(InBoundingBox, InRandomSeed, NumPlanes, PlaneTransforms);
if (PlaneTransforms.IsEmpty())
{
return;
}
FVector Center(0, 0, 0);
DataflowRenderingInterface.SetLineWidth(LineWidthMultiplier);
if (RenderType == EDataflowDebugDrawRenderType::Shaded)
{
DataflowRenderingInterface.SetShaded(true);
DataflowRenderingInterface.SetTranslucent(bTranslucent);
DataflowRenderingInterface.SetWireframe(true);
}
else
{
DataflowRenderingInterface.SetShaded(false);
DataflowRenderingInterface.SetWireframe(true);
}
DataflowRenderingInterface.SetWorldPriority();
DataflowRenderingInterface.SetColor(FLinearColor::Gray);
FRandomStream RandomStream(InRandomSeed);
FNoiseOffsets NoiseOffset(RandomStream);
TArray<FSimpleDebugDrawMesh> DebugMeshes;
DebugMeshes.SetNum(PlaneTransforms.Num());
FTransform CollectionTransform = GetValue(Context, &Transform);
for (int32 PlaneIdx = 0; PlaneIdx < PlaneTransforms.Num(); ++PlaneIdx)
{
if (bRandomizeColors)
{
DataflowRenderingInterface.SetColor(GetRandomColor(ColorRandomSeed, PlaneIdx));
}
const float Width = PlaneSize;
const float Height = PlaneSize;
constexpr int32 MaxCountPerDim = 2000;
const int32 WidthVertexCount = FMath::Min(MaxCountPerDim, Width / NoiseSettings.PointSpacing);
const int32 HeightVertexCount = FMath::Min(MaxCountPerDim, Height / NoiseSettings.PointSpacing);
const FTransform& PlaneTransform = PlaneTransforms[PlaneIdx];
const FVector NoisePivot = CollectionTransform.GetLocation();
FVector Normal = PlaneTransform.GetUnitAxis(EAxis::Z);
FSimpleDebugDrawMesh Mesh;
DebugMeshes[PlaneIdx] = Mesh;
DebugMeshes[PlaneIdx].MakeRectangleMesh(Center, Width, Height, WidthVertexCount, HeightVertexCount);
for (int32 VertexIdx = 0; VertexIdx < DebugMeshes[PlaneIdx].GetMaxVertexIndex(); ++VertexIdx)
{
FVector WorldPos = PlaneTransforms[PlaneIdx].TransformPosition(DebugMeshes[PlaneIdx].Vertices[VertexIdx]);
FVector NewWorldPos = WorldPos + NoiseSettings.NoiseVector(WorldPos - NoisePivot, NoiseOffset).Dot(Normal) * Normal;
DebugMeshes[PlaneIdx].Vertices[VertexIdx] = CollectionTransform.InverseTransformPosition(NewWorldPos);
}
DataflowRenderingInterface.DrawMesh(DebugMeshes[PlaneIdx]);
}
}
}
#endif
/*--------------------------------------------------------------------------------------------------------*/
void FExplodedViewDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection))
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
// Translate collection
FVector InOffset = GetValue(Context, &Offset);
if (InOffset.Length() > UE_KINDA_SMALL_NUMBER)
{
TManagedArray<FVector3f>& Vertex = InCollection.ModifyAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
for (int32 VertexIdx = 0; VertexIdx < Vertex.Num(); ++VertexIdx)
{
Vertex[VertexIdx] += FVector3f(InOffset);
}
}
FFractureEngineFracturing::GenerateExplodedViewAttribute(InCollection, GetValue<FVector>(Context, &Scale), GetValue<float>(Context, &UniformScale));
SetValue(Context, MoveTemp(InCollection), &Collection);
}
}
void FSliceCutterDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection) ||
Out->IsA<FDataflowTransformSelection>(&TransformSelection) ||
Out->IsA<FDataflowTransformSelection>(&NewGeometryTransformSelection))
{
FDataflowTransformSelection InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
int32 ResultGeometryIndex = FFractureEngineFracturing::SliceCutter(InCollection,
InTransformSelection,
GetValue<FBox>(Context, &BoundingBox),
GetValue<int32>(Context, &SlicesX),
GetValue<int32>(Context, &SlicesY),
GetValue<int32>(Context, &SlicesZ),
GetValue<float>(Context, &SliceAngleVariation),
GetValue<float>(Context, &SliceOffsetVariation),
GetValue<int32>(Context, &RandomSeed),
GetValue<float>(Context, &ChanceToFracture),
SplitIslands,
GetValue<float>(Context, &Grout),
GetValue<float>(Context, &Amplitude),
GetValue<float>(Context, &Frequency),
GetValue<float>(Context, &Persistence),
GetValue<float>(Context, &Lacunarity),
GetValue<int32>(Context, &OctaveNumber),
GetValue<float>(Context, &PointSpacing),
AddSamplesForCollision,
GetValue<float>(Context, &CollisionSampleSpacing));
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& TransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
NewSelection.Initialize(TransformIndices.Num(), false);
OriginalSelection.Initialize(TransformIndices.Num(), false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 Idx = ResultGeometryIndex; Idx < TransformIndices.Num(); ++Idx)
{
int32 BoneIdx = TransformIndices[Idx];
NewSelection.SetSelected(BoneIdx);
}
for (int32 Idx = 0; Idx < InTransformSelection.Num(); ++Idx)
{
if (InTransformSelection.IsSelected(Idx))
{
OriginalSelection.SetSelected(Idx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
void FBrickCutterDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection) ||
Out->IsA<FDataflowTransformSelection>(&TransformSelection) ||
Out->IsA<FDataflowTransformSelection>(&NewGeometryTransformSelection))
{
FDataflowTransformSelection InTransformSelection = GetValue<FDataflowTransformSelection>(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected<FDataflowTransformSelection>(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
FBox InBoundingBox = GetValue<FBox>(Context, &BoundingBox);
//
// If not connected set bounds to collection bounds
//
if (!IsConnected<FBox>(&BoundingBox))
{
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
GeometryCollection::Facades::FBoundsFacade BoundsFacade(InCollection);
const FBox& BoundingBoxInCollectionSpace = BoundsFacade.GetBoundingBoxInCollectionSpace();
InBoundingBox = BoundingBoxInCollectionSpace;
}
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
int32 ResultGeometryIndex = FFractureEngineFracturing::BrickCutter(InCollection,
InTransformSelection,
InBoundingBox,
GetValue<FTransform>(Context, &Transform),
Bond,
GetValue<float>(Context, &BrickLength),
GetValue<float>(Context, &BrickHeight),
GetValue<float>(Context, &BrickDepth),
GetValue<int32>(Context, &RandomSeed),
GetValue<float>(Context, &ChanceToFracture),
SplitIslands,
GetValue<float>(Context, &Grout),
GetValue<float>(Context, &Amplitude),
GetValue<float>(Context, &Frequency),
GetValue<float>(Context, &Persistence),
GetValue<float>(Context, &Lacunarity),
GetValue<int32>(Context, &OctaveNumber),
GetValue<float>(Context, &PointSpacing),
AddSamplesForCollision,
GetValue<float>(Context, &CollisionSampleSpacing));
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& TransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
NewSelection.Initialize(TransformIndices.Num(), false);
OriginalSelection.Initialize(TransformIndices.Num(), false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 Idx = ResultGeometryIndex; Idx < TransformIndices.Num(); ++Idx)
{
int32 BoneIdx = TransformIndices[Idx];
NewSelection.SetSelected(BoneIdx);
}
for (int32 Idx = 0; Idx < InTransformSelection.Num(); ++Idx)
{
if (InTransformSelection.IsSelected(Idx))
{
OriginalSelection.SetSelected(Idx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
void FMeshCutterDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection) ||
Out->IsA(&TransformSelection) ||
Out->IsA(&NewGeometryTransformSelection))
{
FDataflowTransformSelection InTransformSelection = GetValue(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
FBox InBoundingBox = GetValue(Context, &BoundingBox);
//
// If not connected set bounds to collection bounds
//
if (!IsConnected(&BoundingBox))
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
GeometryCollection::Facades::FBoundsFacade BoundsFacade(InCollection);
const FBox& BoundingBoxInCollectionSpace = BoundsFacade.GetBoundingBoxInCollectionSpace();
InBoundingBox = BoundingBoxInCollectionSpace;
}
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue(Context, &Collection);
FDynamicMesh3 LocalMesh; // can be used for local storage of a converted static mesh
TArray<const FDynamicMesh3*> UseMeshes;
if (TObjectPtr<UStaticMesh> InCuttingMesh = GetValue(Context, &CuttingStaticMesh))
{
#if WITH_EDITORONLY_DATA
if (FMeshDescription* MeshDescription = bUseHiRes ? InCuttingMesh->GetHiResMeshDescription() : InCuttingMesh->GetMeshDescription(LODLevel))
{
// If HiRes is empty then use LoRes
if (bUseHiRes && MeshDescription->Vertices().Num() == 0)
{
MeshDescription = InCuttingMesh->GetMeshDescription(LODLevel);
}
if (MeshDescription->Vertices().Num() > 0)
{
int32 NumUVLayers = GeometryCollection::UV::GetNumUVLayers(InCollection);
LocalMesh = ConvertMeshDescriptionToCuttingDynamicMesh(MeshDescription, NumUVLayers);
UseMeshes.Add(&LocalMesh);
}
}
#else
// TODO: for runtime usage, could try to fallback to the render mesh (if available on CPU)
ensureMsgf(false, TEXT("FMeshCutterDataflowNode's Static Mesh support is currently editor-only."));
#endif
}
const TArray<TObjectPtr<UDynamicMesh>>& InDynamicMeshes = GetValue(Context, &CuttingDynamicMeshes);
for (TObjectPtr<UDynamicMesh> MeshObj : InDynamicMeshes)
{
if (MeshObj && MeshObj->GetMeshPtr())
{
UseMeshes.Add(MeshObj->GetMeshPtr());
}
}
if (UseMeshes.Num() > 0)
{
const int32 InRandomSeed = GetValue(Context, &RandomSeed);
const int32 InNumberToScatter = GetValue(Context, &NumberToScatter);
const int32 InGridX = GetValue(Context, &GridX);
const int32 InGridY = GetValue(Context, &GridY);
const int32 InGridZ = GetValue(Context, &GridZ);
const float InVariability = GetValue(Context, &Variability);
const float InMinScaleFactor = GetValue(Context, &MinScaleFactor);
const float InMaxScaleFactor = GetValue(Context, &MaxScaleFactor);
const float InRollRange = GetValue(Context, &RollRange);
const float InPitchRange = GetValue(Context, &PitchRange);
const float InYawRange = GetValue(Context, &YawRange);
const FTransform InTransform = GetValue(Context, &Transform);
const float InChanceToFracture = GetValue(Context, &ChanceToFracture);
const float InCollisionSampleSpacing = GetValue(Context, &CollisionSampleSpacing);
// Note: per-cut mesh selection is not currently a dataflow input
const EMeshCutterPerCutMeshSelection InPerCutMeshSelection = PerCutMeshSelection;
TArray<FTransform> MeshTransforms;
if (CutDistribution == EMeshCutterCutDistribution::SingleCut)
{
MeshTransforms.Add(InTransform);
}
else
{
FFractureEngineFracturing::GenerateMeshTransforms(MeshTransforms,
InBoundingBox,
InRandomSeed,
CutDistribution,
InNumberToScatter,
InGridX,
InGridY,
InGridZ,
InVariability,
InMinScaleFactor,
InMaxScaleFactor,
bRandomOrientation,
InRollRange,
InPitchRange,
InYawRange);
}
int32 ResultGeometryIndex = FFractureEngineFracturing::MeshArrayCutter(MeshTransforms,
InCollection,
InTransformSelection,
UseMeshes,
InPerCutMeshSelection,
InRandomSeed,
InChanceToFracture,
SplitIslands,
InCollisionSampleSpacing);
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& TransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
NewSelection.Initialize(TransformIndices.Num(), false);
OriginalSelection.Initialize(TransformIndices.Num(), false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 Idx = ResultGeometryIndex; Idx < TransformIndices.Num(); ++Idx)
{
int32 BoneIdx = TransformIndices[Idx];
NewSelection.SetSelected(BoneIdx);
}
for (int32 Idx = 0; Idx < InTransformSelection.Num(); ++Idx)
{
if (InTransformSelection.IsSelected(Idx))
{
OriginalSelection.SetSelected(Idx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
FUniformFractureDataflowNode::FUniformFractureDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&TransformSelection);
RegisterInputConnection(&Transform);
RegisterInputConnection(&MinVoronoiSites)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&MaxVoronoiSites)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&InternalMaterialID);
RegisterInputConnection(&RandomSeed)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&ChanceToFracture)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Grout)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Amplitude)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Frequency)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Persistence)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Lacunarity)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&OctaveNumber)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&PointSpacing)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&CollisionSampleSpacing)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterOutputConnection(&Collection, &Collection);
RegisterOutputConnection(&TransformSelection, &TransformSelection);
RegisterOutputConnection(&NewGeometryTransformSelection);
}
void FUniformFractureDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection) ||
Out->IsA(&TransformSelection) ||
Out->IsA(&NewGeometryTransformSelection))
{
FDataflowTransformSelection InTransformSelection = GetValue(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
if (InTransformSelection.AnySelected())
{
FManagedArrayCollection InCollection = GetValue<FManagedArrayCollection>(Context, &Collection);
FUniformFractureSettings UniformFractureSettings;
UniformFractureSettings.Transform = GetValue(Context, &Transform);
UniformFractureSettings.MinVoronoiSites = GetValue(Context, &MinVoronoiSites);
UniformFractureSettings.MaxVoronoiSites = GetValue(Context, &MaxVoronoiSites);
UniformFractureSettings.InternalMaterialID = InternalMaterialID;
UniformFractureSettings.RandomSeed = GetValue(Context, &RandomSeed);
UniformFractureSettings.ChanceToFracture = GetValue(Context, &ChanceToFracture);
UniformFractureSettings.GroupFracture = GroupFracture;
UniformFractureSettings.SplitIslands = SplitIslands;
UniformFractureSettings.Grout = GetValue(Context, &Grout);
UniformFractureSettings.NoiseSettings.Amplitude = GetValue(Context, &Amplitude);
UniformFractureSettings.NoiseSettings.Frequency = GetValue(Context, &Frequency);
UniformFractureSettings.NoiseSettings.Persistence = GetValue(Context, &Persistence);
UniformFractureSettings.NoiseSettings.Lacunarity = GetValue(Context, &Lacunarity);
UniformFractureSettings.NoiseSettings.Octaves = GetValue(Context, &OctaveNumber);
UniformFractureSettings.NoiseSettings.PointSpacing = GetValue(Context, &PointSpacing);
UniformFractureSettings.AddSamplesForCollision = AddSamplesForCollision;
UniformFractureSettings.CollisionSampleSpacing = GetValue(Context, &CollisionSampleSpacing);
int32 ResultGeometryIndex = FFractureEngineFracturing::UniformFracture(
InCollection,
InTransformSelection,
UniformFractureSettings);
FDataflowTransformSelection NewSelection;
FDataflowTransformSelection OriginalSelection;
if (ResultGeometryIndex != INDEX_NONE)
{
if (InCollection.HasAttribute("TransformIndex", FGeometryCollection::GeometryGroup))
{
const TManagedArray<int32>& GeometryToTransformIndices = InCollection.GetAttribute<int32>("TransformIndex", FGeometryCollection::GeometryGroup);
const int32 NumTransforms = InCollection.NumElements(FGeometryCollection::TransformGroup);
NewSelection.Initialize(NumTransforms, false);
OriginalSelection.Initialize(NumTransforms, false);
// The newly fractured pieces are added to the end of the transform array (starting position is ResultGeometryIndex)
for (int32 GeometryIdx = ResultGeometryIndex; GeometryIdx < GeometryToTransformIndices.Num(); ++GeometryIdx)
{
const int32 TransformIdx = GeometryToTransformIndices[GeometryIdx];
NewSelection.SetSelected(TransformIdx);
}
for (int32 TransformIdx = 0; TransformIdx < InTransformSelection.Num(); ++TransformIdx)
{
if (InTransformSelection.IsSelected(TransformIdx))
{
OriginalSelection.SetSelected(TransformIdx);
}
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OriginalSelection, &TransformSelection);
SetValue(Context, NewSelection, &NewGeometryTransformSelection);
return;
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, InTransformSelection, &TransformSelection);
SetValue(Context, FDataflowTransformSelection(), &NewGeometryTransformSelection);
}
}
/** ------------------------------------------------------------------------------------------------------------ **/
FVisualizeFractureDataflowNode::FVisualizeFractureDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&RandomSeed)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Level)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&ExplodeAmount)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Scale)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&Offset)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterOutputConnection(&Collection, &Collection);
}
void FVisualizeFractureDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection))
{
FManagedArrayCollection InCollection = GetValue(Context, &Collection);
// Translate collection
FVector InOffset = GetValue(Context, &Offset);
if (InOffset.Length() > UE_KINDA_SMALL_NUMBER)
{
TManagedArray<FVector3f>& Vertex = InCollection.ModifyAttribute<FVector3f>("Vertex", FGeometryCollection::VerticesGroup);
for (int32 VertexIdx = 0; VertexIdx < Vertex.Num(); ++VertexIdx)
{
Vertex[VertexIdx] += FVector3f(InOffset);
}
}
const int32 InLevel = GetValue(Context, &Level);
check(InLevel >= 0);
if (bApplyExplodedView)
{
FFractureEngineFracturing::GenerateExplodedViewAttribute(
InCollection,
GetValue(Context, &Scale),
GetValue(Context, &ExplodeAmount),
InLevel);
}
if (bApplyColor)
{
if (InCollection.HasAttribute("BoneColor", FGeometryCollection::TransformGroup))
{
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>("BoneColor", FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
const int32 InRandomSeed = GetValue(Context, &RandomSeed);
FRandomStream RandomStream(NumBones + InRandomSeed);
// Clear BoneColors
FFractureEngineFracturing::InitColors(InCollection);
if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByParent &&
InCollection.HasAttribute(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup))
{
FFractureEngineFracturing::SetBoneColorByParent(InCollection, RandomStream, InLevel, RandomColorRangeMin, RandomColorRangeMax);
}
else if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByLevel &&
InCollection.HasAttribute(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup))
{
FFractureEngineFracturing::SetBoneColorByLevel(InCollection, InLevel);
}
else if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByCluster &&
InCollection.HasAttribute(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup))
{
// This what the Geometry Tools uses
FFractureEngineFracturing::SetBoneColorByCluster(InCollection, RandomStream, InLevel, RandomColorRangeMin, RandomColorRangeMax);
}
else if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByLeafLevel &&
InCollection.HasAttribute(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup))
{
FFractureEngineFracturing::SetBoneColorByLeafLevel(InCollection, InLevel);
}
else if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByLeaf)
{
FFractureEngineFracturing::SetBoneColorByLeaf(InCollection, RandomStream, InLevel, RandomColorRangeMin, RandomColorRangeMax);
}
else if (ColoringType == EDataflowVisualizeFractureColoringType::ColorByAttr)
{
FFractureEngineFracturing::SetBoneColorByAttr(InCollection,
Attribute,
Min.MinAttrValue,
Max.MaxAttrValue,
Min.MinColor,
Max.MaxColor);
}
// Transfer BoneColors to VertexColor
FFractureEngineFracturing::TransferBoneColorToVertexColor(InCollection);
SetValue(Context, MoveTemp(InCollection), &Collection);
}
}
else
{
SafeForwardInput(Context, &Collection, &Collection);
}
}
}
FSetFloatAttributeDataflowNode::FSetFloatAttributeDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&RandomSeed)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterInputConnection(&NoiseScale)
.SetCanHidePin(true)
.SetPinIsHidden(true);
RegisterOutputConnection(&Collection, &Collection);
RegisterOutputConnection(&FloatArray);
}
void FSetFloatAttributeDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection) || Out->IsA(&FloatArray))
{
if (IsConnected(&Collection))
{
FManagedArrayCollection InCollection = GetValue(Context, &Collection);
const FName AttrName = FName(*Attribute);
if (InCollection.HasAttribute(AttrName, FGeometryCollection::TransformGroup))
{
TManagedArray<float>& AttrValues = InCollection.ModifyAttribute<float>(AttrName, FGeometryCollection::TransformGroup);
const int32 NumAttrValues = AttrValues.Num();
TArray<float> OutFloatArray; OutFloatArray.AddUninitialized(NumAttrValues);
if (Method == EDataflowSetFloatArrayMethod::Random)
{
const int32 InRandomSeed = GetValue(Context, &RandomSeed);
FRandomStream RandomStream(InRandomSeed);
for (int32 Idx = 0; Idx < NumAttrValues; ++Idx)
{
AttrValues[Idx] = RandomStream.FRandRange(0.f, 1.f);
OutFloatArray[Idx] = AttrValues[Idx];
}
}
else if (Method == EDataflowSetFloatArrayMethod::Noise)
{
const int32 InNoiseScale = GetValue(Context, &NoiseScale);
const TManagedArray<int32>& TransformToGeometryIndices = InCollection.GetAttribute<int32>(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<FBox>& BoundingBoxes = InCollection.GetAttribute<FBox>(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup);
for (int32 Idx = 0; Idx < NumAttrValues; ++Idx)
{
int32 GeometryIdx = TransformToGeometryIndices[Idx];
if (GeometryIdx != -1)
{
FVector Center = BoundingBoxes[GeometryIdx].GetCenter();
AttrValues[Idx] = 0.5f * FMath::PerlinNoise3D(InNoiseScale * Center) + 1.f;
}
else
{
AttrValues[Idx] = 0.f;
}
}
}
else if (Method == EDataflowSetFloatArrayMethod::ByBoundingBox)
{
const TManagedArray<int32>& TransformToGeometryIndices = InCollection.GetAttribute<int32>(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<FBox>& BoundingBoxes = InCollection.GetAttribute<FBox>(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup);
// Compute BoundingBox for the entire collection
FBox BBox = FBox(ForceInit);
for (int32 Idx = 0; Idx < NumAttrValues; ++Idx)
{
int32 GeometryIdx = TransformToGeometryIndices[Idx];
if (GeometryIdx != -1)
{
BBox += BoundingBoxes[GeometryIdx];
}
}
for (int32 Idx = 0; Idx < NumAttrValues; ++Idx)
{
int32 GeometryIdx = TransformToGeometryIndices[Idx];
if (GeometryIdx != -1)
{
FVector Center = BoundingBoxes[GeometryIdx].GetCenter();
AttrValues[Idx] = (Center.X - BBox.Min.X) / (BBox.Max.X - BBox.Min.X);
}
else
{
AttrValues[Idx] = 0.f;
}
}
}
SetValue(Context, MoveTemp(InCollection), &Collection);
SetValue(Context, OutFloatArray, &FloatArray);
return;
}
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, TArray<float>(), &FloatArray);
}
}