// 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(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 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 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(), &Points); } } } void FUniformScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA>(&Points)) { const FBox& BBox = GetValue(Context, &BoundingBox); if (BBox.GetVolume() > 0.f) { FRandomStream RandStream(GetValue(Context, &RandomSeed)); const FVector Extent(BBox.Max - BBox.Min); const int32 NumPoints = RandStream.RandRange(GetValue(Context, &MinNumberOfPoints), GetValue(Context, &MaxNumberOfPoints)); TArray 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(), &Points); } } } void FUniformScatterPointsDataflowNode_v2::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA>(&Points)) { const FBox& BBox = GetValue(Context, &BoundingBox); if (BBox.GetVolume() > 0.f) { FRandomStream RandStream(GetValue(Context, &RandomSeed)); const FVector Extent(BBox.Max - BBox.Min); const int32 NumPoints = RandStream.RandRange(GetValue(Context, &MinNumberOfPoints), GetValue(Context, &MaxNumberOfPoints)); TArray 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(), &Points); } } } void FRadialScatterPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA>(&Points)) { const FVector::FReal RadialStep = GetValue(Context, &Radius) / GetValue(Context, &RadialSteps); const FVector::FReal AngularStep = 2 * PI / GetValue(Context, &AngularSteps); FRandomStream RandStream(GetValue(Context, &RandomSeed)); FVector UpVector(GetValue(Context, &Normal)); UpVector.Normalize(); FVector BasisX, BasisY; UpVector.FindBestAxisVectors(BasisX, BasisY); TArray PointsArr; FVector::FReal Len = RadialStep * .5; for (int32 ii = 0; ii < GetValue(Context, &RadialSteps); ++ii, Len += RadialStep) { FVector::FReal Angle = FMath::DegreesToRadians(GetValue(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(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>(&Points)) { const FBox InBoundingBox = GetValue(Context, &BoundingBox); const FVector InCenter = GetValue(Context, &Center); const FVector InNormal = GetValue(Context, &Normal); const int32 InRandomSeed = GetValue(Context, &RandomSeed); const int32 InAngularSteps = GetValue(Context, &AngularSteps); const float InAngleOffset = GetValue(Context, &AngleOffset); const float InAngularNoise = GetValue(Context, &AngularNoise); const float InRadius = GetValue(Context, &Radius); const int32 InRadialSteps = GetValue(Context, &RadialSteps); const float InRadialStepExponent = GetValue(Context, &RadialStepExponent); const float InRadialMinStep = GetValue(Context, &RadialMinStep); const float InRadialNoise = GetValue(Context, &RadialNoise); const float InRadialVariability = GetValue(Context, &RadialVariability); const float InAngularVariability = GetValue(Context, &AngularVariability); const float InAxialVariability = GetValue(Context, &AxialVariability); TArray 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 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 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>(&Points)) { const FBox& BBox = GetValue(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(Context, &NumberOfPointsInX), 0, MaxPointsPerDim); const int32 NumPointsInY = FMath::Clamp(GetValue(Context, &NumberOfPointsInY), 0, MaxPointsPerDim); const int32 NumPointsInZ = FMath::Clamp(GetValue(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(Context, &RandomSeed)); TArray 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(Context, &MaxRandomDisplacementX), RandStream.FRandRange(-1.f, 1.f) * GetValue(Context, &MaxRandomDisplacementY), RandStream.FRandRange(-1.f, 1.f) * GetValue(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(), &Points); } } else { // ERROR: Invalid BoundingBox input SetValue(Context, TArray(), &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 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& InPointsA = GetValue(Context, &PointsA); const TArray& InPointsB = GetValue(Context, &PointsB); TArray 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(&Collection)) { const FDataflowTransformSelection& InTransformSelection = GetValue(Context, &TransformSelection); if (IsConnected(&TransformSelection)) { if (InTransformSelection.AnySelected()) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); FFractureEngineFracturing::VoronoiFracture(InCollection, InTransformSelection, GetValue>(Context, &Points), FTransform::Identity, (int32)GetValue(Context, &RandomSeed), GetValue(Context, &ChanceToFracture), true, 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)); SetValue(Context, MoveTemp(InCollection), &Collection); return; } } const FManagedArrayCollection& InCollection = GetValue(Context, &Collection); SetValue(Context, InCollection, &Collection); } } void FVoronoiFractureDataflowNode_v2::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& SelectionArr = TransformSelectionFacade.SelectAll(); FDataflowTransformSelection NewTransformSelection; NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false); NewTransformSelection.SetFromArray(SelectionArr); InTransformSelection = NewTransformSelection; } if (InTransformSelection.AnySelected()) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); int32 ResultGeometryIndex = FFractureEngineFracturing::VoronoiFracture(InCollection, InTransformSelection, GetValue>(Context, &Points), GetValue(Context, &Transform), 0, // RandomSeed is not used in Voronoi fracture, it is used in the source point generation 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& GeometryToTransformIndices = InCollection.GetAttribute("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(&Collection)) { const FDataflowTransformSelection& InTransformSelection = GetValue(Context, &TransformSelection); if (IsConnected(&TransformSelection)) { if (InTransformSelection.AnySelected()) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); FFractureEngineFracturing::PlaneCutter(InCollection, InTransformSelection, GetValue(Context, &BoundingBox), FTransform::Identity, NumPlanes, (int32)GetValue(Context, &RandomSeed), 1.f, true, GetValue(Context, &Grout), GetValue(Context, &Amplitude), GetValue(Context, &Frequency), GetValue(Context, &Persistence), GetValue(Context, &Lacunarity), GetValue(Context, &OctaveNumber), GetValue(Context, &PointSpacing), GetValue(Context, &AddSamplesForCollision), GetValue(Context, &CollisionSampleSpacing)); SetValue(Context, MoveTemp(InCollection), &Collection); return; } } const FManagedArrayCollection& InCollection = GetValue(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(RandomStream.FRandRange(128, 255)); const uint8 G = static_cast(RandomStream.FRandRange(128, 255)); const uint8 B = static_cast(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(&Collection) || Out->IsA(&TransformSelection) || Out->IsA(&NewGeometryTransformSelection)) { FManagedArrayCollection InCollection = GetValue(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(Context, &TransformSelection); // // If not connected select everything by default // if (!IsConnected(&TransformSelection)) { GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection); const TArray& 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& TransformIndices = InCollection.GetAttribute("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(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(Context, &RandomSeed); TArray 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 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(&Collection)) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); // Translate collection FVector InOffset = GetValue(Context, &Offset); if (InOffset.Length() > UE_KINDA_SMALL_NUMBER) { TManagedArray& Vertex = InCollection.ModifyAttribute("Vertex", FGeometryCollection::VerticesGroup); for (int32 VertexIdx = 0; VertexIdx < Vertex.Num(); ++VertexIdx) { Vertex[VertexIdx] += FVector3f(InOffset); } } FFractureEngineFracturing::GenerateExplodedViewAttribute(InCollection, GetValue(Context, &Scale), GetValue(Context, &UniformScale)); SetValue(Context, MoveTemp(InCollection), &Collection); } } void FSliceCutterDataflowNode::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& SelectionArr = TransformSelectionFacade.SelectAll(); FDataflowTransformSelection NewTransformSelection; NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false); NewTransformSelection.SetFromArray(SelectionArr); InTransformSelection = NewTransformSelection; } if (InTransformSelection.AnySelected()) { FManagedArrayCollection InCollection = GetValue(Context, &Collection); int32 ResultGeometryIndex = FFractureEngineFracturing::SliceCutter(InCollection, InTransformSelection, GetValue(Context, &BoundingBox), GetValue(Context, &SlicesX), GetValue(Context, &SlicesY), GetValue(Context, &SlicesZ), GetValue(Context, &SliceAngleVariation), GetValue(Context, &SliceOffsetVariation), 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& TransformIndices = InCollection.GetAttribute("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(&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& 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); int32 ResultGeometryIndex = FFractureEngineFracturing::BrickCutter(InCollection, InTransformSelection, InBoundingBox, GetValue(Context, &Transform), Bond, GetValue(Context, &BrickLength), GetValue(Context, &BrickHeight), GetValue(Context, &BrickDepth), 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& TransformIndices = InCollection.GetAttribute("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& 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 UseMeshes; if (TObjectPtr 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>& InDynamicMeshes = GetValue(Context, &CuttingDynamicMeshes); for (TObjectPtr 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 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& TransformIndices = InCollection.GetAttribute("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(Context, &Collection); GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection); const TArray& SelectionArr = TransformSelectionFacade.SelectAll(); FDataflowTransformSelection NewTransformSelection; NewTransformSelection.Initialize(InCollection.NumElements(FGeometryCollection::TransformGroup), false); NewTransformSelection.SetFromArray(SelectionArr); InTransformSelection = NewTransformSelection; } if (InTransformSelection.AnySelected()) { FManagedArrayCollection InCollection = GetValue(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& GeometryToTransformIndices = InCollection.GetAttribute("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& Vertex = InCollection.ModifyAttribute("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& BoneColors = InCollection.ModifyAttribute("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& AttrValues = InCollection.ModifyAttribute(AttrName, FGeometryCollection::TransformGroup); const int32 NumAttrValues = AttrValues.Num(); TArray 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& TransformToGeometryIndices = InCollection.GetAttribute(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup); const TManagedArray& BoundingBoxes = InCollection.GetAttribute(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& TransformToGeometryIndices = InCollection.GetAttribute(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup); const TManagedArray& BoundingBoxes = InCollection.GetAttribute(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(), &FloatArray); } }