Files
UnrealEngine/Engine/Plugins/Experimental/Fracture/Source/FractureEngine/Private/FractureEngineFracturing.cpp
2025-05-18 13:04:45 +08:00

1724 lines
63 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FractureEngineFracturing.h"
#include "FractureEngineSelection.h"
#include "Dataflow/DataflowSelection.h"
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/TransformCollection.h"
#include "Voronoi/Voronoi.h"
#include "GeometryCollection/Facades/CollectionMeshFacade.h"
#include "Algo/RemoveIf.h"
#include "GeometryCollection/Facades/CollectionTransformSelectionFacade.h"
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "FractureEngineMaterials.h"
#include "Dataflow/DataflowSettings.h"
// Local helpers
namespace UE::Private::FractureHelpers
{
static void GenerateVoronoiSites(const FBox& InBoundingBox,
const int32 InMinVoronoiSites,
const int32 InMaxVoronoiSites,
const int32 InRandomSeed,
TArray<FVector>& OutSites)
{
FRandomStream RandStream(InRandomSeed);
const FVector Extent(InBoundingBox.Max - InBoundingBox.Min);
const int32 SiteCount = RandStream.RandRange(InMinVoronoiSites, InMaxVoronoiSites);
OutSites.Reserve(OutSites.Num() + SiteCount);
for (int32 ii = 0; ii < SiteCount; ++ii)
{
OutSites.Emplace(InBoundingBox.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
}
}
static float GetMaxVertexMovement(const float InGrout,
const float InAmplitude,
const int32 InOctaveNumber,
const float InPersistence)
{
float MaxDisp = InGrout;
float AmplitudeScaled = InAmplitude;
for (int32 OctaveIdx = 0; OctaveIdx < InOctaveNumber; OctaveIdx++, AmplitudeScaled *= InPersistence)
{
MaxDisp += FMath::Abs(AmplitudeScaled);
}
return MaxDisp;
}
static FBox GetVoronoiBounds(const FBox& InBoundingBox,
const TArray<FVector>& Sites,
const float InGrout,
const float InAmplitude,
const int32 InOctaveNumber,
const float InPersistence)
{
FBox VoronoiBounds = InBoundingBox;
if (Sites.Num() > 0)
{
VoronoiBounds += FBox(Sites);
}
return VoronoiBounds.ExpandBy(GetMaxVertexMovement(InGrout, InAmplitude, InOctaveNumber, InPersistence) + KINDA_SMALL_NUMBER);
}
static void GenerateTemporaryGuids(FManagedArrayCollection& InCollection, int32 InStartIdx, bool InForceInit)
{
bool bNeedsInit = false;
if (!InCollection.HasAttribute("GUID", FTransformCollection::TransformGroup))
{
FManagedArrayCollection::FConstructionParameters Params(FName(""), false);
InCollection.AddAttribute<FGuid>("GUID", FTransformCollection::TransformGroup, Params);
bNeedsInit = true;
}
if (bNeedsInit || InForceInit)
{
TManagedArray<FGuid>& Guids = InCollection.ModifyAttribute<FGuid>("GUID", FTransformCollection::TransformGroup);
for (int32 Idx = InStartIdx; Idx < Guids.Num(); ++Idx)
{
Guids[Idx] = FGuid::NewGuid();
}
}
}
static void ProcessNewlyFracturedBones(FGeometryCollection& OutGeomCollection, int32 FirstNewGeometryIndex, int32 NewInternalMaterialID = INDEX_NONE)
{
if (FirstNewGeometryIndex == INDEX_NONE)
{
return;
}
// Assign internal material
if (NewInternalMaterialID > INDEX_NONE)
{
FFractureEngineMaterials::SetMaterialOnGeometryAfter(OutGeomCollection, FirstNewGeometryIndex, FFractureEngineMaterials::ETargetFaces::InternalFaces, NewInternalMaterialID);
OutGeomCollection.ReindexMaterials();
}
// Generate GUIDs
GenerateTemporaryGuids(OutGeomCollection, FirstNewGeometryIndex, true);
}
static int32 UniformFractureProc(
TUniquePtr<FGeometryCollection>& OutGeomCollection,
const TArray<int32>& InTransformSelectionArr,
const FUniformFractureProcSettings& InUniformFractureProcSettings)
{
TArray<FVector> Sites;
GenerateVoronoiSites(InUniformFractureProcSettings.BBox,
InUniformFractureProcSettings.MinVoronoiSites,
InUniformFractureProcSettings.MaxVoronoiSites,
InUniformFractureProcSettings.RandomSeed,
Sites);
FBox VoronoiBounds = GetVoronoiBounds(InUniformFractureProcSettings.BBox,
Sites,
InUniformFractureProcSettings.Grout,
InUniformFractureProcSettings.NoiseSettings.Amplitude,
InUniformFractureProcSettings.NoiseSettings.Octaves,
InUniformFractureProcSettings.NoiseSettings.Persistence);
FVector Origin = InUniformFractureProcSettings.Transform.GetTranslation();
for (FVector& Site : Sites)
{
Site -= Origin;
}
VoronoiBounds.Min -= Origin;
VoronoiBounds.Max -= Origin;
FVoronoiDiagram Voronoi(Sites, VoronoiBounds, .1f);
FPlanarCells VoronoiPlanarCells = FPlanarCells(Sites, Voronoi);
VoronoiPlanarCells.InternalSurfaceMaterials.NoiseSettings = InUniformFractureProcSettings.NoiseSettings;
int32 FirstNewGeometryIndex = CutMultipleWithPlanarCells(VoronoiPlanarCells,
*OutGeomCollection,
InTransformSelectionArr,
InUniformFractureProcSettings.Grout,
InUniformFractureProcSettings.CollisionSampleSpacing,
InUniformFractureProcSettings.RandomSeed,
InUniformFractureProcSettings.Transform,
true,
true,
nullptr,
Origin,
InUniformFractureProcSettings.SplitIslands);
// failed to cut
if (FirstNewGeometryIndex == INDEX_NONE)
{
return INDEX_NONE;
}
ProcessNewlyFracturedBones(*OutGeomCollection, FirstNewGeometryIndex, InUniformFractureProcSettings.InternalMaterialID);
return FirstNewGeometryIndex;
}
static void SelectLeavesHelper(const FGeometryCollection& GeometryCollection, FDataflowTransformSelection& InOutTransformSelection, int32 BoneIdx)
{
if (!ensure(BoneIdx < InOutTransformSelection.Num() && GeometryCollection.SimulationType.IsValidIndex(BoneIdx)))
{
return;
}
if (GeometryCollection.SimulationType[BoneIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid)
{
InOutTransformSelection.SetNotSelected(BoneIdx);
for (int32 ChildIdx : GeometryCollection.Children[BoneIdx])
{
SelectLeavesHelper(GeometryCollection, InOutTransformSelection, ChildIdx);
}
}
else
{
InOutTransformSelection.SetSelected(BoneIdx);
}
}
static void ConvertToLeafSelection(const FGeometryCollection& GeometryCollection, FDataflowTransformSelection& InOutTransformSelection)
{
if (!InOutTransformSelection.IsValidForCollection(GeometryCollection))
{
TArray<int32> ValidSelection = InOutTransformSelection.AsArrayValidated(GeometryCollection);
InOutTransformSelection.InitFromArray(GeometryCollection, ValidSelection);
}
for (int32 BoneIdx = 0; BoneIdx < InOutTransformSelection.Num(); ++BoneIdx)
{
if (InOutTransformSelection.IsSelected(BoneIdx))
{
SelectLeavesHelper(GeometryCollection, InOutTransformSelection, BoneIdx);
}
}
}
}
static void AddAdditionalAttributesIfRequired(FManagedArrayCollection& InOutCollection)
{
FGeometryCollectionClusteringUtility::UpdateHierarchyLevelOfChildren(InOutCollection, -1);
}
static bool GetValidGeoCenter(FManagedArrayCollection& InOutCollection,
const TManagedArray<int32>& TransformToGeometryIndex,
const TArray<FTransform>& Transforms,
const TManagedArray<int32>& Parents,
const TManagedArray<TSet<int32>>& Children,
const TManagedArray<FBox>& BoundingBoxes,
const TManagedArray<int32>& SimulationTypes,
int32 TransformIndex,
FVector& OutGeoCenter)
{
if (SimulationTypes[TransformIndex] == FGeometryCollection::ESimulationTypes::FST_Rigid)
{
OutGeoCenter = Transforms[TransformIndex].TransformPosition(BoundingBoxes[TransformToGeometryIndex[TransformIndex]].GetCenter());
return true;
}
else if (SimulationTypes[TransformIndex] == FGeometryCollection::ESimulationTypes::FST_None) // ie this is embedded geometry
{
int32 Parent = Parents[TransformIndex];
int32 ParentGeo = Parent != INDEX_NONE ? TransformToGeometryIndex[Parent] : INDEX_NONE;
if (ensureMsgf(ParentGeo != INDEX_NONE, TEXT("Embedded geometry should always have a rigid geometry parent! Geometry collection may be malformed.")))
{
OutGeoCenter = Transforms[Parents[TransformIndex]].TransformPosition(BoundingBoxes[ParentGeo].GetCenter());
}
else
{
return false; // no valid value to return
}
return true;
}
else
{
FVector AverageCenter;
int32 ValidVectors = 0;
for (int32 ChildIndex : Children[TransformIndex])
{
if (GetValidGeoCenter(InOutCollection, TransformToGeometryIndex, Transforms, Parents, Children, BoundingBoxes, SimulationTypes, ChildIndex, OutGeoCenter))
{
if (ValidVectors == 0)
{
AverageCenter = OutGeoCenter;
}
else
{
AverageCenter += OutGeoCenter;
}
++ValidVectors;
}
}
if (ValidVectors > 0)
{
OutGeoCenter = AverageCenter / ValidVectors;
return true;
}
}
return false;
}
//
// TODO: Rewrite this using facades
//
void FFractureEngineFracturing::GenerateExplodedViewAttribute(FManagedArrayCollection& InOutCollection, const FVector& InScale, const float InUniformScale, const int32 InViewFractureLevel, const int32 InMaxFractureLevel)
{
// Check if InOutCollection is not empty
if (InOutCollection.HasAttribute(FTransformCollection::TransformAttribute, FGeometryCollection::TransformGroup))
{
InOutCollection.AddAttribute<FVector3f>("ExplodedVector", FGeometryCollection::TransformGroup, FManagedArrayCollection::FConstructionParameters(FName(), false));
check(InOutCollection.HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup));
TManagedArray<FVector3f>& ExplodedVectors = InOutCollection.ModifyAttribute<FVector3f>("ExplodedVector", FGeometryCollection::TransformGroup);
const TManagedArray<FTransform3f>& Transforms = InOutCollection.GetAttribute<FTransform3f>(FTransformCollection::TransformAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& TransformToGeometryIndices = InOutCollection.GetAttribute<int32>(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<FBox>& BoundingBoxes = InOutCollection.GetAttribute<FBox>(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup);
// Make sure we have valid "Level"
AddAdditionalAttributesIfRequired(InOutCollection);
const TManagedArray<int32>& Levels = InOutCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FTransformCollection::TransformGroup);
const TManagedArray<int32>& Parents = InOutCollection.GetAttribute<int32>(FTransformCollection::ParentAttribute, FTransformCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = InOutCollection.GetAttribute<TSet<int32>>(FTransformCollection::ChildrenAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimulationTypes = InOutCollection.GetAttribute<int32>(FGeometryCollection::SimulationTypeAttribute, FGeometryCollection::TransformGroup);
int32 MaxFractureLevel = InMaxFractureLevel;
for (int32 Idx = 0, ni = Transforms.Num(); Idx < ni; ++Idx)
{
if (Levels[Idx] > MaxFractureLevel)
MaxFractureLevel = Levels[Idx];
}
TArray<FTransform> TransformArr;
GeometryCollectionAlgo::GlobalMatrices(Transforms, Parents, TransformArr);
TArray<FVector> TransformedCenters;
TransformedCenters.SetNumUninitialized(TransformArr.Num());
int32 TransformsCount = 0;
FVector Center(ForceInitToZero);
for (int32 Idx = 0, ni = Transforms.Num(); Idx < ni; ++Idx)
{
ExplodedVectors[Idx] = FVector3f::ZeroVector;
FVector GeoCenter;
if (GetValidGeoCenter(InOutCollection, TransformToGeometryIndices, TransformArr, Parents, Children, BoundingBoxes, SimulationTypes, Idx, GeoCenter))
{
TransformedCenters[Idx] = GeoCenter;
if ((InViewFractureLevel < 0) || Levels[Idx] == InViewFractureLevel)
{
Center += TransformedCenters[Idx];
++TransformsCount;
}
}
}
Center /= TransformsCount;
for (int Level = 1; Level <= MaxFractureLevel; Level++)
{
for (int32 Idx = 0, ni = TransformArr.Num(); Idx < ni; ++Idx)
{
if ((InViewFractureLevel < 0) || Levels[Idx] == InViewFractureLevel)
{
FVector ScaleVec = InScale * InUniformScale;
ExplodedVectors[Idx] = (FVector3f)(TransformedCenters[Idx] - Center) * (FVector3f)ScaleVec;
}
else
{
if (Parents[Idx] > -1)
{
ExplodedVectors[Idx] = ExplodedVectors[Parents[Idx]];
}
}
}
}
}
}
static float GetMaxVertexMovement(float Grout, float Amplitude, int OctaveNumber, float Persistence)
{
float MaxDisp = Grout;
float AmplitudeScaled = Amplitude;
for (int32 OctaveIdx = 0; OctaveIdx < OctaveNumber; OctaveIdx++, AmplitudeScaled *= Persistence)
{
MaxDisp += FMath::Abs(AmplitudeScaled);
}
return MaxDisp;
}
static void RandomReduceSelection(FDataflowTransformSelection& InOutTransformSelection, int32 InRandomSeed, float InProbToKeep)
{
FRandomStream RandStream(InRandomSeed);
for (int32 BoneIdx = 0; BoneIdx < InOutTransformSelection.Num(); ++BoneIdx)
{
if (InOutTransformSelection.IsSelected(BoneIdx))
{
if (RandStream.GetFraction() >= InProbToKeep) // range does not include 1, so if ProbToKeep is 1 this will never remove
{
InOutTransformSelection.SetNotSelected(BoneIdx);
}
}
}
}
int32 FFractureEngineFracturing::VoronoiFracture(FManagedArrayCollection& InOutCollection,
FDataflowTransformSelection InTransformSelection,
TArray<FVector> InSites,
const FTransform& InTransform,
int32 InRandomSeed,
float InChanceToFracture,
bool InSplitIslands,
float InGrout,
float InAmplitude,
float InFrequency,
float InPersistence,
float InLacunarity,
int32 InOctaveNumber,
float InPointSpacing,
bool InAddSamplesForCollision,
float InCollisionSampleSpacing)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
if (InSites.Num() > 0)
{
//
// Compute BoundingBox for InCollection
//
FBox BoundingBox(ForceInit);
if (InOutCollection.HasAttribute(FTransformCollection::TransformAttribute, FGeometryCollection::TransformGroup) &&
InOutCollection.HasAttribute(FTransformCollection::ParentAttribute, FGeometryCollection::TransformGroup) &&
InOutCollection.HasAttribute(FGeometryCollection::TransformIndexAttribute, FGeometryCollection::GeometryGroup) &&
InOutCollection.HasAttribute(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup))
{
const TManagedArray<FTransform3f>& Transforms = InOutCollection.GetAttribute<FTransform3f>(FTransformCollection::TransformAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& ParentIndices = InOutCollection.GetAttribute<int32>(FTransformCollection::ParentAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& TransformIndices = InOutCollection.GetAttribute<int32>(FGeometryCollection::TransformIndexAttribute, FGeometryCollection::GeometryGroup);
const TManagedArray<FBox>& BoundingBoxes = InOutCollection.GetAttribute<FBox>(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup);
TArray<FMatrix> TmpGlobalMatrices;
GeometryCollectionAlgo::GlobalMatrices(Transforms, ParentIndices, TmpGlobalMatrices);
if (TmpGlobalMatrices.Num() > 0)
{
for (int32 BoxIdx = 0; BoxIdx < BoundingBoxes.Num(); ++BoxIdx)
{
const int32 TransformIndex = TransformIndices[BoxIdx];
BoundingBox += BoundingBoxes[BoxIdx].TransformBy(TmpGlobalMatrices[TransformIndex]);
}
}
FVector Origin = InTransform.GetTranslation();
for (FVector& Site : InSites)
{
Site -= Origin;
}
//
// Compute Voronoi Bounds
//
FBox VoronoiBounds = BoundingBox;
VoronoiBounds += FBox(InSites);
VoronoiBounds = VoronoiBounds.ExpandBy(GetMaxVertexMovement(InGrout, InAmplitude, InOctaveNumber, InPersistence) + KINDA_SMALL_NUMBER);
//
// Voronoi Fracture
//
FNoiseSettings NoiseSettings;
NoiseSettings.Amplitude = InAmplitude;
NoiseSettings.Frequency = InFrequency;
NoiseSettings.Octaves = InOctaveNumber;
NoiseSettings.PointSpacing = InPointSpacing;
NoiseSettings.Lacunarity = InLacunarity;
NoiseSettings.Persistence = InPersistence;
FVoronoiDiagram Voronoi(InSites, VoronoiBounds, .1f);
FPlanarCells VoronoiPlanarCells = FPlanarCells(InSites, Voronoi);
VoronoiPlanarCells.InternalSurfaceMaterials.NoiseSettings = NoiseSettings;
RandomReduceSelection(InTransformSelection, InRandomSeed, InChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
if (!FFractureEngineSelection::IsBoneSelectionValid(InOutCollection, TransformSelectionArr))
{
return INDEX_NONE;
}
int ResultGeometryIndex = CutMultipleWithPlanarCells(VoronoiPlanarCells, *GeomCollection, TransformSelectionArr, InGrout, InCollisionSampleSpacing, InRandomSeed, InTransform, true, true, nullptr, Origin, InSplitIslands);
UE::Private::FractureHelpers::ProcessNewlyFracturedBones(*GeomCollection, ResultGeometryIndex);
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
}
}
return INDEX_NONE;
}
void FFractureEngineFracturing::GenerateSliceTransforms(const FBox& InBoundingBox,
const int32 InRandomSeed,
const int32 InNumPlanes,
TArray<FTransform>& OutCuttingPlaneTransforms)
{
FRandomStream RandStream(InRandomSeed);
FBox Bounds = InBoundingBox;
const FVector Extent(Bounds.Max - Bounds.Min);
OutCuttingPlaneTransforms.Reserve(OutCuttingPlaneTransforms.Num() + InNumPlanes);
for (int32 Idx = 0; Idx < InNumPlanes; ++Idx)
{
FVector Position(Bounds.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
OutCuttingPlaneTransforms.Emplace(FTransform(FRotator(RandStream.FRand() * 360.0f, RandStream.FRand() * 360.0f, 0.0f), Position));
}
}
int32 FFractureEngineFracturing::PlaneCutter(FManagedArrayCollection& InOutCollection,
FDataflowTransformSelection InTransformSelection,
const FBox& InBoundingBox,
const FTransform& InTransform,
int32 InNumPlanes,
int32 InRandomSeed,
float InChanceToFracture,
bool InSplitIslands,
float InGrout,
float InAmplitude,
float InFrequency,
float InPersistence,
float InLacunarity,
int32 InOctaveNumber,
float InPointSpacing,
bool InAddSamplesForCollision,
float InCollisionSampleSpacing)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
TArray<FPlane> CuttingPlanes;
TArray<FTransform> CuttingPlaneTransforms;
FFractureEngineFracturing::GenerateSliceTransforms(InBoundingBox, InRandomSeed, InNumPlanes, CuttingPlaneTransforms);
for (const FTransform& Transform : CuttingPlaneTransforms)
{
CuttingPlanes.Add(FPlane(Transform.GetLocation(), Transform.GetUnitAxis(EAxis::Z)));
}
FInternalSurfaceMaterials InternalSurfaceMaterials;
FNoiseSettings NoiseSettings;
if (InAmplitude > 0.f)
{
NoiseSettings.Amplitude = InAmplitude;
NoiseSettings.Frequency = InFrequency;
NoiseSettings.Lacunarity = InLacunarity;
NoiseSettings.Persistence = InPersistence;
NoiseSettings.Octaves = InOctaveNumber;
NoiseSettings.PointSpacing = InPointSpacing;
InternalSurfaceMaterials.NoiseSettings = NoiseSettings;
}
float CollisionSampleSpacingVal = InCollisionSampleSpacing;
float GroutVal = InGrout;
RandomReduceSelection(InTransformSelection, InRandomSeed, InChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
if (!FFractureEngineSelection::IsBoneSelectionValid(InOutCollection, TransformSelectionArr))
{
return INDEX_NONE;
}
int ResultGeometryIndex = CutMultipleWithMultiplePlanes(CuttingPlanes, InternalSurfaceMaterials, *GeomCollection, TransformSelectionArr, GroutVal, CollisionSampleSpacingVal, InRandomSeed, InTransform, true, nullptr, InSplitIslands);
UE::Private::FractureHelpers::ProcessNewlyFracturedBones(*GeomCollection, ResultGeometryIndex);
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
return INDEX_NONE;
}
void FFractureEngineFracturing::GenerateSliceTransforms(TArray<FTransform>& InOutCuttingPlaneTransforms,
const FBox& InBoundingBox,
int32 InSlicesX,
int32 InSlicesY,
int32 InSlicesZ,
int32 InRandomSeed,
float InSliceAngleVariation,
float InSliceOffsetVariation)
{
const FBox& Bounds = InBoundingBox;
const FVector& Min = Bounds.Min;
const FVector& Max = Bounds.Max;
const FVector Center = Bounds.GetCenter();
const FVector Extents(Max - Min);
const FVector HalfExtents(Extents * 0.5f);
const FVector Step(Extents.X / (InSlicesX + 1), Extents.Y / (InSlicesY + 1), Extents.Z / (InSlicesZ + 1));
InOutCuttingPlaneTransforms.Reserve(InSlicesX * InSlicesY * InSlicesZ);
FRandomStream RandomStream(InRandomSeed);
const float SliceAngleVariationInRadians = FMath::DegreesToRadians(InSliceAngleVariation);
const FVector XMin(-Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
const FVector XMax(Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
for (int32 xx = 0; xx < InSlicesX; ++xx)
{
const FVector SlicePosition(FVector(Min.X, Center.Y, Center.Z) + FVector((Step.X * xx) + Step.X, 0.0f, 0.0f) + RandomStream.VRand() * RandomStream.GetFraction() * InSliceOffsetVariation);
FTransform Transform(FQuat(FVector::RightVector, FMath::DegreesToRadians(90)), SlicePosition);
const FQuat RotA(FVector::RightVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
const FQuat RotB(FVector::ForwardVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
Transform.ConcatenateRotation(FQuat(RotA * RotB));
InOutCuttingPlaneTransforms.Emplace(Transform);
}
const FVector YMin(-Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
const FVector YMax(Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
for (int32 yy = 0; yy < InSlicesY; ++yy)
{
const FVector SlicePosition(FVector(Center.X, Min.Y, Center.Z) + FVector(0.0f, (Step.Y * yy) + Step.Y, 0.0f) + RandomStream.VRand() * RandomStream.GetFraction() * InSliceOffsetVariation);
FTransform Transform(FQuat(FVector::ForwardVector, FMath::DegreesToRadians(90)), SlicePosition);
const FQuat RotA(FVector::RightVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
const FQuat RotB(FVector::ForwardVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
Transform.ConcatenateRotation(RotA * RotB);
InOutCuttingPlaneTransforms.Emplace(Transform);
}
const FVector ZMin(-Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
const FVector ZMax(Bounds.GetExtent() * FVector(1.0f, 1.0f, 0.0f));
for (int32 zz = 0; zz < InSlicesZ; ++zz)
{
const FVector SlicePosition(FVector(Center.X, Center.Y, Min.Z) + FVector(0.0f, 0.0f, (Step.Z * zz) + Step.Z) + RandomStream.VRand() * RandomStream.GetFraction() * InSliceOffsetVariation);
FTransform Transform(SlicePosition);
const FQuat RotA(FVector::RightVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
const FQuat RotB(FVector::ForwardVector, RandomStream.FRandRange(0.0f, SliceAngleVariationInRadians));
Transform.ConcatenateRotation(RotA * RotB);
InOutCuttingPlaneTransforms.Emplace(Transform);
}
}
static void ClearProximity(FGeometryCollection* GeometryCollection)
{
if (GeometryCollection->HasAttribute("Proximity", FGeometryCollection::GeometryGroup))
{
GeometryCollection->RemoveAttribute("Proximity", FGeometryCollection::GeometryGroup);
}
}
int32 FFractureEngineFracturing::SliceCutter(FManagedArrayCollection& InOutCollection,
FDataflowTransformSelection InTransformSelection,
const FBox& InBoundingBox,
int32 InSlicesX,
int32 InSlicesY,
int32 InSlicesZ,
float InSliceAngleVariation,
float InSliceOffsetVariation,
int32 InRandomSeed,
float InChanceToFracture,
bool InSplitIslands,
float InGrout,
float InAmplitude,
float InFrequency,
float InPersistence,
float InLacunarity,
int32 InOctaveNumber,
float InPointSpacing,
bool InAddSamplesForCollision,
float InCollisionSampleSpacing)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
TArray<FTransform> LocalCuttingPlanesTransforms;
GenerateSliceTransforms(LocalCuttingPlanesTransforms,
InBoundingBox,
InSlicesX,
InSlicesY,
InSlicesZ,
InRandomSeed,
InSliceAngleVariation,
InSliceOffsetVariation);
TArray<FPlane> CuttingPlanes;
CuttingPlanes.Reserve(LocalCuttingPlanesTransforms.Num());
for (const FTransform& Transform : LocalCuttingPlanesTransforms)
{
CuttingPlanes.Add(FPlane(Transform.GetLocation(), Transform.GetUnitAxis(EAxis::Z)));
}
FInternalSurfaceMaterials InternalSurfaceMaterials;
FNoiseSettings NoiseSettings;
if (InAmplitude > 0.f)
{
NoiseSettings.Amplitude = InAmplitude;
NoiseSettings.Frequency = InFrequency;
NoiseSettings.Lacunarity = InLacunarity;
NoiseSettings.Persistence = InPersistence;
NoiseSettings.Octaves = InOctaveNumber;
NoiseSettings.PointSpacing = InPointSpacing;
InternalSurfaceMaterials.NoiseSettings = NoiseSettings;
}
float CollisionSampleSpacingVal = InCollisionSampleSpacing;
float GroutVal = InGrout;
RandomReduceSelection(InTransformSelection, InRandomSeed, InChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
if (!FFractureEngineSelection::IsBoneSelectionValid(InOutCollection, TransformSelectionArr))
{
return INDEX_NONE;
}
// Proximity is invalidated.
ClearProximity(GeomCollection.Get());
int ResultGeometryIndex = CutMultipleWithMultiplePlanes(CuttingPlanes, InternalSurfaceMaterials, *GeomCollection, TransformSelectionArr, GroutVal, CollisionSampleSpacingVal, InRandomSeed, FTransform().Identity, true, nullptr, InSplitIslands);
UE::Private::FractureHelpers::ProcessNewlyFracturedBones(*GeomCollection, ResultGeometryIndex);
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
return INDEX_NONE;
}
namespace FractureToolBrickLocals
{
// Calculate total number of bricks based on given dimensions and the extent of the object to be fractured.
// If the input is not valid or the result is too large, this functions returns -1.
// It is possible that we are dealing with incredibly large meshes and small brick dimensions. Doing the
// calculations in double and checking for NaNs will catch cases where for integer we would have to jump through
// multiple hoops to make sure we are not dealing with overflow. And since the limit for the number of bricks is
// comparably low, we do not need to worry about loss in precision for very large integers.
// For example, running this calculation with brick dimensions of 1 for the Sky Sphere will result in integer
// overflow.
static int64 CalculateNumBricks(const FVector& Dimensions, const FVector& Extents)
{
if (Dimensions.GetMin() <= 0 || Extents.GetMin() <= 0)
{
return -1;
}
const FVector NumBricksPerDim(ceil(Extents.X / Dimensions.X), ceil(Extents.Y / Dimensions.Y), ceil(Extents.Z / Dimensions.Z));
if (NumBricksPerDim.ContainsNaN())
{
return -1;
}
const double NumBricks = NumBricksPerDim.X * NumBricksPerDim.Y * NumBricksPerDim.Z;
if (FMath::IsNaN(NumBricks))
{
return -1;
}
return static_cast<int64>(NumBricks);
}
static FVector GetBrickDimensions(const float InBrickLength,
const float InBrickHeight,
const float InBrickDepth,
const FVector& InExtents)
{
// Limit for the total number of bricks.
const int64 NumBricksLimit = 8192;
FVector Dimensions(InBrickLength, InBrickDepth, InBrickHeight);
// Early out if we have inputs we cannot deal with. If this call to CalculateNumBricks is fine then any other
// call to it will be fine, too, and we do not need to check for invalid results again.
int64 NumBricks = CalculateNumBricks(Dimensions, InExtents);
if (NumBricks < 0)
{
return FVector::ZeroVector;
}
if (NumBricks > NumBricksLimit)
{
const int64 InputNumBricks = NumBricks;
// Determine dimensions safely within the brick limit by iteratively doubling the brick size.
FVector SafeDimensions = Dimensions;
int64 SafeNumBricks;
do
{
SafeDimensions *= 2;
SafeNumBricks = CalculateNumBricks(SafeDimensions, InExtents);
} while (SafeNumBricks > NumBricksLimit);
// Maximize brick dimensions to fit within the brick limit via iterative interval halving.
const int32 IterationsMax = 10;
int32 Iterations = 0;
do
{
const FVector MidDimensions = (Dimensions + SafeDimensions) / 2;
const int64 MidNumBricks = CalculateNumBricks(MidDimensions, InExtents);
if (MidNumBricks > NumBricksLimit)
{
Dimensions = MidDimensions;
NumBricks = MidNumBricks;
}
else
{
SafeDimensions = MidDimensions;
SafeNumBricks = MidNumBricks;
}
} while (++Iterations < IterationsMax);
Dimensions = SafeDimensions;
NumBricks = SafeNumBricks;
// UE_LOG(LogFractureTool, Warning, TEXT("Brick Fracture: Current brick dimensions of %f x %f x %f would result in %d bricks. "
// "Reduced brick dimensions to %f x %f x %f resulting in %d bricks to stay within maximum number of %d bricks."),
// InBrickLength, InBrickDepth, InBrickHeight, InputNumBricks,
// Dimensions.X, Dimensions.Y, Dimensions.Z, NumBricks, NumBricksLimit);
}
return Dimensions;
}
}
void FFractureEngineFracturing::AddBoxEdges(TArray<TTuple<FVector, FVector>>& InOutEdges, const FVector& InMin, const FVector& InMax)
{
InOutEdges.Emplace(MakeTuple(InMin, FVector(InMin.X, InMax.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(InMin, FVector(InMin.X, InMin.Y, InMax.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMin.X, InMax.Y, InMax.Z), FVector(InMin.X, InMax.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMin.X, InMax.Y, InMax.Z), FVector(InMin.X, InMin.Y, InMax.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMax.X, InMin.Y, InMin.Z), FVector(InMax.X, InMax.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMax.X, InMin.Y, InMin.Z), FVector(InMax.X, InMin.Y, InMax.Z)));
InOutEdges.Emplace(MakeTuple(InMax, FVector(InMax.X, InMax.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(InMax, FVector(InMax.X, InMin.Y, InMax.Z)));
InOutEdges.Emplace(MakeTuple(InMin, FVector(InMax.X, InMin.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMin.X, InMin.Y, InMax.Z), FVector(InMax.X, InMin.Y, InMax.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMin.X, InMax.Y, InMin.Z), FVector(InMax.X, InMax.Y, InMin.Z)));
InOutEdges.Emplace(MakeTuple(FVector(InMin.X, InMax.Y, InMax.Z), InMax));
}
void FFractureEngineFracturing::GenerateBrickTransforms(const FBox& InBounds,
TArray<FTransform>& InOutBrickTransforms,
const EFractureBrickBondEnum InBond,
const float InBrickLength,
const float InBrickHeight,
const float InBrickDepth,
TArray<TTuple<FVector, FVector>>& InOutEdges)
{
const FVector& Min = InBounds.Min;
const FVector& Max = InBounds.Max;
const FVector Extents(InBounds.Max - InBounds.Min);
// Determine brick dimensions (length, depth, height) and make sure we do not exceed the limit for the number of bricks.
// If we would simply use the input dimensions, we are prone to running out of memory and/or exceeding the storage capabilities of TArray, and crash.
const FVector BrickDimensions = FractureToolBrickLocals::GetBrickDimensions(InBrickLength, InBrickHeight, InBrickDepth, Extents);
// Early out if we have inputs we cannot deal with.
if (BrickDimensions == FVector::ZeroVector)
{
return;
}
// Reserve correct amount of memory to avoid re-allocations.
InOutBrickTransforms.Reserve(FractureToolBrickLocals::CalculateNumBricks(BrickDimensions, Extents));
const FVector BrickHalfDimensions(BrickDimensions * 0.5);
const FQuat HeaderRotation(FVector::UpVector, 1.5708);
if (InBond == EFractureBrickBondEnum::Dataflow_FractureBrickBond_Stretcher)
{
bool OddY = false;
for (float yy = 0.f; yy <= Extents.Y; yy += BrickDimensions.Y)
{
bool Oddline = false;
for (float zz = BrickHalfDimensions.Z; zz <= Extents.Z; zz += BrickDimensions.Z)
{
for (float xx = 0.f; xx <= Extents.X; xx += BrickDimensions.X)
{
FVector BrickPosition(Min + FVector(Oddline ^ OddY ? xx : xx + BrickHalfDimensions.X, yy, zz));
InOutBrickTransforms.Emplace(FTransform(BrickPosition));
}
Oddline = !Oddline;
}
OddY = !OddY;
}
}
else if (InBond == EFractureBrickBondEnum::Dataflow_FractureBrickBond_Stack)
{
bool OddY = false;
for (float yy = 0.f; yy <= Extents.Y; yy += BrickDimensions.Y)
{
for (float zz = BrickHalfDimensions.Z; zz <= Extents.Z; zz += BrickDimensions.Z)
{
for (float xx = 0.f; xx <= Extents.X; xx += BrickDimensions.X)
{
FVector BrickPosition(Min + FVector(OddY ? xx : xx + BrickHalfDimensions.X, yy, zz));
InOutBrickTransforms.Emplace(FTransform(BrickPosition));
}
}
OddY = !OddY;
}
}
else if (InBond == EFractureBrickBondEnum::Dataflow_FractureBrickBond_English)
{
float HalfLengthDepthDifference = BrickHalfDimensions.X - BrickHalfDimensions.Y - BrickHalfDimensions.Y;
bool OddY = false;
for (float yy = 0.f; yy <= Extents.Y; yy += BrickDimensions.Y)
{
bool Oddline = false;
for (float zz = BrickHalfDimensions.Z; zz <= Extents.Z; zz += BrickDimensions.Z)
{
if (Oddline && !OddY) // header row
{
for (float xx = 0.f; xx <= Extents.X; xx += BrickDimensions.Y)
{
FVector BrickPosition(Min + FVector(Oddline ^ OddY ? xx : xx + BrickHalfDimensions.Y, yy + BrickHalfDimensions.Y, zz));
InOutBrickTransforms.Emplace(FTransform(HeaderRotation, BrickPosition));
}
}
else if (!Oddline) // stretchers
{
for (float xx = 0.f; xx <= Extents.X; xx += BrickDimensions.X)
{
FVector BrickPosition(Min + FVector(Oddline ^ OddY ? xx : xx + BrickHalfDimensions.X, OddY ? yy + HalfLengthDepthDifference : yy - HalfLengthDepthDifference, zz));
InOutBrickTransforms.Emplace(FTransform(BrickPosition));
}
}
Oddline = !Oddline;
}
OddY = !OddY;
}
}
else if (InBond == EFractureBrickBondEnum::Dataflow_FractureBrickBond_Header)
{
bool OddY = false;
for (float yy = 0.f; yy <= Extents.Y; yy += BrickDimensions.X)
{
bool Oddline = false;
for (float zz = BrickHalfDimensions.Z; zz <= Extents.Z; zz += BrickDimensions.Z)
{
for (float xx = 0.f; xx <= Extents.X; xx += BrickDimensions.Y)
{
FVector BrickPosition(Min + FVector(Oddline ^ OddY ? xx : xx + BrickHalfDimensions.Y, yy, zz));
InOutBrickTransforms.Emplace(FTransform(HeaderRotation, BrickPosition));
}
Oddline = !Oddline;
}
OddY = !OddY;
}
}
else if (InBond == EFractureBrickBondEnum::Dataflow_FractureBrickBond_Flemish)
{
float HalfLengthDepthDifference = BrickHalfDimensions.X - BrickDimensions.Y;
bool OddY = false;
int32 RowY = 0;
for (float yy = 0.f; yy <= Extents.Y; yy += BrickDimensions.Y)
{
bool OddZ = false;
for (float zz = BrickHalfDimensions.Z; zz <= Extents.Z; zz += BrickDimensions.Z)
{
bool OddX = OddZ;
for (float xx = 0.f; xx <= Extents.X; xx += BrickHalfDimensions.X + BrickHalfDimensions.Y)
{
FVector BrickPosition(Min + FVector(xx, yy, zz));
if (OddX)
{
if (OddY) // runner
{
InOutBrickTransforms.Emplace(FTransform(BrickPosition + FVector(0, HalfLengthDepthDifference, 0))); // runner
}
else
{
InOutBrickTransforms.Emplace(FTransform(BrickPosition - FVector(0, HalfLengthDepthDifference, 0))); // runner
}
}
else if (!OddY) // header
{
InOutBrickTransforms.Emplace(FTransform(HeaderRotation, BrickPosition + FVector(0, BrickHalfDimensions.Y, 0))); // header
}
OddX = !OddX;
}
OddZ = !OddZ;
}
OddY = !OddY;
++RowY;
}
}
const FVector BrickMax(BrickHalfDimensions);
const FVector BrickMin(-BrickHalfDimensions);
for (const auto& Transform : InOutBrickTransforms)
{
AddBoxEdges(InOutEdges, Transform.TransformPosition(BrickMin), Transform.TransformPosition(BrickMax));
}
}
int32 FFractureEngineFracturing::BrickCutter(FManagedArrayCollection& InOutCollection,
FDataflowTransformSelection InTransformSelection,
const FBox& InBoundingBox,
const FTransform& InTransform,
EFractureBrickBondEnum InBond,
float InBrickLength,
float InBrickHeight,
float InBrickDepth,
int32 InRandomSeed,
float InChanceToFracture,
bool InSplitIslands,
float InGrout,
float InAmplitude,
float InFrequency,
float InPersistence,
float InLacunarity,
int32 InOctaveNumber,
float InPointSpacing,
bool InAddSamplesForCollision,
float InCollisionSampleSpacing)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
TArray<FTransform> BrickTransforms;
TArray<TTuple<FVector, FVector>> Edges;
TArray<TTuple<FVector, FVector>> Boxes;
BrickTransforms.Empty();
const FBox Bounds = InBoundingBox;
GenerateBrickTransforms(Bounds,
BrickTransforms,
InBond,
InBrickLength,
InBrickHeight,
InBrickDepth,
Edges);
// Get the same brick dimensions that were used in GenerateBrickTransform.
// If we cannot deal with the input data then the brick dimensions will be zero, but we do not need to
// explicitly handle that since it will only affect some local variables. The BrickTransforms will be empty
// and there are no further side effects.
const FVector BrickDimensions = FractureToolBrickLocals::GetBrickDimensions(InBrickLength, InBrickHeight, InBrickDepth, Bounds.Max - Bounds.Min);
const FVector BrickHalfDimensions(BrickDimensions * 0.5);
const FQuat HeaderRotation(FVector::UpVector, 1.5708);
TArray<FBox> BricksToCut;
// space the bricks by the grout setting, constrained to not erase the bricks
const float MinDim = FMath::Min3(BrickHalfDimensions.X, BrickHalfDimensions.Y, BrickHalfDimensions.Z);
const float HalfGrout = FMath::Clamp(0.5f * InGrout, 0, MinDim * 0.98f);
const FVector HalfBrick(BrickHalfDimensions - HalfGrout);
const FBox BrickBox(-HalfBrick, HalfBrick);
FTransform ContextTransform = InTransform;
FVector Origin = ContextTransform.GetTranslation();
for (const FTransform& Trans : BrickTransforms)
{
FTransform ToApply = Trans * FTransform(-Origin);
BricksToCut.Add(BrickBox.TransformBy(ToApply));
}
FInternalSurfaceMaterials InternalSurfaceMaterials;
FNoiseSettings NoiseSettings;
if (InAmplitude > 0.f)
{
NoiseSettings.Amplitude = InAmplitude;
NoiseSettings.Frequency = InFrequency;
NoiseSettings.Lacunarity = InLacunarity;
NoiseSettings.Persistence = InPersistence;
NoiseSettings.Octaves = InOctaveNumber;
NoiseSettings.PointSpacing = InPointSpacing;
InternalSurfaceMaterials.NoiseSettings = NoiseSettings;
}
float CollisionSampleSpacingVal = InCollisionSampleSpacing;
RandomReduceSelection(InTransformSelection, InRandomSeed, InChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
if (!FFractureEngineSelection::IsBoneSelectionValid(InOutCollection, TransformSelectionArr))
{
return INDEX_NONE;
}
float GroutVal = 0.f; // CutterSettings->Grout; // Note: Grout is currently baked directly into the brick cells above
const bool bBricksAreTouching = InGrout <= UE_KINDA_SMALL_NUMBER;
FPlanarCells Cells = FPlanarCells(BricksToCut, bBricksAreTouching);
int32 ResultGeometryIndex = CutMultipleWithPlanarCells(Cells, *GeomCollection, TransformSelectionArr, GroutVal, InPointSpacing, InRandomSeed, InTransform, true, true, nullptr, Origin, InSplitIslands);
UE::Private::FractureHelpers::ProcessNewlyFracturedBones(*GeomCollection, ResultGeometryIndex);
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
return INDEX_NONE;
}
void FFractureEngineFracturing::GenerateMeshTransforms(TArray<FTransform>& MeshTransforms,
const FBox& InBoundingBox,
const int32 InRandomSeed,
const EMeshCutterCutDistribution InCutDistribution,
const int32 InNumberToScatter,
const int32 InGridX,
const int32 InGridY,
const int32 InGridZ,
const float InVariability,
const float InMinScaleFactor,
const float InMaxScaleFactor,
const bool InRandomOrientation,
const float InRollRange,
const float InPitchRange,
const float InYawRange)
{
FRandomStream RandStream(InRandomSeed);
FBox Bounds = InBoundingBox;
const FVector Extent(Bounds.Max - Bounds.Min);
TArray<FVector> Positions;
if (InCutDistribution == EMeshCutterCutDistribution::UniformRandom)
{
Positions.Reserve(InNumberToScatter);
for (int32 Idx = 0; Idx < InNumberToScatter; ++Idx)
{
Positions.Emplace(Bounds.Min + FVector(RandStream.FRand(), RandStream.FRand(), RandStream.FRand()) * Extent);
}
}
else if (InCutDistribution == EMeshCutterCutDistribution::Grid)
{
Positions.Reserve(InGridX * InGridY * InGridZ);
auto ToFrac = [](int32 Val, int32 NumVals) -> FVector::FReal
{
return (FVector::FReal(Val) + FVector::FReal(.5)) / FVector::FReal(NumVals);
};
for (int32 X = 0; X < InGridX; ++X)
{
FVector::FReal XFrac = ToFrac(X, InGridX);
for (int32 Y = 0; Y < InGridY; ++Y)
{
FVector::FReal YFrac = ToFrac(Y, InGridY);
for (int32 Z = 0; Z < InGridZ; ++Z)
{
FVector::FReal ZFrac = ToFrac(Z, InGridZ);
Positions.Emplace(Bounds.Min + FVector(XFrac, YFrac, ZFrac) * Extent);
}
}
}
for (FVector& Position : Positions)
{
Position += (RandStream.VRand() * RandStream.FRand() * InVariability);
}
}
MeshTransforms.Reserve(MeshTransforms.Num() + Positions.Num());
for (const FVector& Position : Positions)
{
const FVector ScaleVec(RandStream.FRandRange(InMinScaleFactor, InMaxScaleFactor));
FRotator Orientation = FRotator::ZeroRotator;
if (InRandomOrientation)
{
Orientation = FRotator(
RandStream.FRandRange(-InPitchRange, InPitchRange),
RandStream.FRandRange(-InYawRange, InYawRange),
RandStream.FRandRange(-InRollRange, InRollRange)
);
}
MeshTransforms.Emplace(FTransform(Orientation, Position, ScaleVec));
}
}
int32 FFractureEngineFracturing::MeshArrayCutter(TArray<FTransform>& MeshTransforms,
FManagedArrayCollection& InOutCollection,
const FDataflowTransformSelection& InTransformSelectionConst,
TArrayView<const UE::Geometry::FDynamicMesh3*> InDynCuttingMeshes,
const EMeshCutterPerCutMeshSelection PerCutMeshSelection,
const int32 InRandomSeed,
const float InChanceToFracture,
const bool InSplitIslands,
const float InCollisionSampleSpacing)
{
if (InOutCollection.NumElements(FTransformCollection::TransformGroup) == 0)
{
// empty collection, early exit
return -1;
}
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
// Note: Noise not currently supported
FInternalSurfaceMaterials InternalSurfaceMaterials;
int32 ResultGeometryIndex = -1;
const int32 OriginalNumTransforms = InOutCollection.NumElements(FGeometryCollection::TransformGroup);
FRandomStream RandStream(InRandomSeed);
FDataflowTransformSelection InTransformSelection = InTransformSelectionConst;
RandomReduceSelection(InTransformSelection, InRandomSeed, InChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
int32 SequentialMeshIndex = 0;
for (const FTransform& ScatterTransform : MeshTransforms)
{
auto ApplyCut =
[&ScatterTransform, &InternalSurfaceMaterials, &GeomCollection, &TransformSelectionArr, InCollisionSampleSpacing, InSplitIslands, &ResultGeometryIndex]
(const UE::Geometry::FDynamicMesh3& CuttingMesh)
{
constexpr bool bSetDefaultInternalMaterialsFromCollection = true;
int32 Index = CutWithMesh(CuttingMesh, ScatterTransform, InternalSurfaceMaterials, *GeomCollection, TransformSelectionArr, InCollisionSampleSpacing, FTransform::Identity, bSetDefaultInternalMaterialsFromCollection, nullptr, InSplitIslands);
UE::Private::FractureHelpers::ProcessNewlyFracturedBones(*GeomCollection, Index);
int32 NewLen = Algo::RemoveIf(TransformSelectionArr, [&GeomCollection](int32 Bone)
{
return !GeomCollection->IsVisible(Bone); // remove already-fractured pieces from the to-cut list
});
TransformSelectionArr.SetNum(NewLen);
if (ResultGeometryIndex == -1)
{
ResultGeometryIndex = Index;
}
if (Index > -1)
{
const int32 TransformIdx = GeomCollection->TransformIndex[Index];
// after a successful cut, also consider any new bones added by the cut
for (int32 NewBoneIdx = TransformIdx; NewBoneIdx < GeomCollection->NumElements(FGeometryCollection::TransformGroup); NewBoneIdx++)
{
TransformSelectionArr.Add(NewBoneIdx);
}
}
};
switch (PerCutMeshSelection)
{
case EMeshCutterPerCutMeshSelection::All:
for (const UE::Geometry::FDynamicMesh3* Mesh : InDynCuttingMeshes)
{
ApplyCut(*Mesh);
}
break;
case EMeshCutterPerCutMeshSelection::Random:
ApplyCut(*InDynCuttingMeshes[RandStream.RandHelper(InDynCuttingMeshes.Num())]);
break;
case EMeshCutterPerCutMeshSelection::Sequential:
ApplyCut(*InDynCuttingMeshes[SequentialMeshIndex]);
SequentialMeshIndex = (SequentialMeshIndex + 1) % InDynCuttingMeshes.Num();
break;
default:
checkNoEntry();
}
}
if (ResultGeometryIndex > -1)
{
TArray<int32> ToRemove;
for (int32 NewIdx = OriginalNumTransforms; NewIdx < GeomCollection->NumElements(FGeometryCollection::TransformGroup); ++NewIdx)
{
if (GeomCollection->IsRigid(NewIdx))
{
int32 ParentIdx = GeomCollection->Parent[NewIdx];
if (ParentIdx >= OriginalNumTransforms)
{
do
{
ParentIdx = GeomCollection->Parent[ParentIdx];
} while (GeomCollection->Parent[ParentIdx] >= OriginalNumTransforms);
GeomCollection->ParentTransforms(ParentIdx, NewIdx);
}
}
else
{
ToRemove.Add(NewIdx);
}
}
FManagedArrayCollection::FProcessingParameters ProcessingParams;
ProcessingParams.bDoValidation = false;
GeomCollection->RemoveElements(FGeometryCollection::TransformGroup, ToRemove, ProcessingParams);
}
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
return INDEX_NONE;
}
int32 FFractureEngineFracturing::MeshCutter(TArray<FTransform>& MeshTransforms,
FManagedArrayCollection& InOutCollection,
const FDataflowTransformSelection& InTransformSelection,
const UE::Geometry::FDynamicMesh3& InDynCuttingMesh,
const int32 InRandomSeed,
const float InChanceToFracture,
const bool InSplitIslands,
const float InCollisionSampleSpacing)
{
const UE::Geometry::FDynamicMesh3* LocalCutterPtr = &InDynCuttingMesh;
return MeshArrayCutter(MeshTransforms, InOutCollection, InTransformSelection,
TArrayView<const UE::Geometry::FDynamicMesh3*>(&LocalCutterPtr, 1), EMeshCutterPerCutMeshSelection::All,
InRandomSeed, InChanceToFracture, InSplitIslands, InCollisionSampleSpacing);
}
int32 FFractureEngineFracturing::UniformFracture(
FManagedArrayCollection& InOutCollection,
FDataflowTransformSelection InTransformSelection,
const FUniformFractureSettings& InUniformFractureSettings)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InOutCollection.NewCopy<FGeometryCollection>()))
{
RandomReduceSelection(InTransformSelection, InUniformFractureSettings.RandomSeed, InUniformFractureSettings.ChanceToFracture);
UE::Private::FractureHelpers::ConvertToLeafSelection(*GeomCollection, InTransformSelection);
TArray<int32> TransformSelectionArr = InTransformSelection.AsArrayValidated(*GeomCollection);
if (!FFractureEngineSelection::IsBoneSelectionValid(InOutCollection, TransformSelectionArr))
{
return INDEX_NONE;
}
// Update global transforms and bounds
const TManagedArray<FTransform3f>& Transform = GeomCollection->GetAttribute<FTransform3f>(FTransformCollection::TransformAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& TransformToGeometryIndex = GeomCollection->GetAttribute<int32>(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<FBox>& BoundingBoxes = GeomCollection->GetAttribute<FBox>(FGeometryCollection::BoundingBoxAttribute, FGeometryCollection::GeometryGroup);
TArray<FTransform> Transforms;
GeometryCollectionAlgo::GlobalMatrices(Transform, GeomCollection->Parent, Transforms);
int32 TransformCount = Transform.Num();
TMap<int32, FBox> BoundsToBone;
for (int32 Index = 0; Index < TransformCount; ++Index)
{
if (TransformToGeometryIndex[Index] > INDEX_NONE)
{
BoundsToBone.Add(Index, BoundingBoxes[TransformToGeometryIndex[Index]].TransformBy(Transforms[Index]));
}
}
FBox BoundingBox(ForceInit);
if (InUniformFractureSettings.GroupFracture)
{
FBox Bounds(ForceInit);
for (int32 TransformIndex : TransformSelectionArr)
{
if (TransformToGeometryIndex[TransformIndex] > INDEX_NONE)
{
Bounds += BoundsToBone[TransformIndex];
}
}
BoundingBox = Bounds;
// Fracture
FUniformFractureProcSettings UniformFractureProcSettings;
UniformFractureProcSettings.BBox = BoundingBox;
UniformFractureProcSettings.Transform = InUniformFractureSettings.Transform;
UniformFractureProcSettings.MinVoronoiSites = InUniformFractureSettings.MinVoronoiSites;
UniformFractureProcSettings.MaxVoronoiSites = InUniformFractureSettings.MaxVoronoiSites;
UniformFractureProcSettings.InternalMaterialID = InUniformFractureSettings.InternalMaterialID;
UniformFractureProcSettings.RandomSeed = InUniformFractureSettings.RandomSeed;
UniformFractureProcSettings.SplitIslands = InUniformFractureSettings.SplitIslands;
UniformFractureProcSettings.Grout = InUniformFractureSettings.Grout;
UniformFractureProcSettings.NoiseSettings = InUniformFractureSettings.NoiseSettings;
UniformFractureProcSettings.AddSamplesForCollision = InUniformFractureSettings.AddSamplesForCollision;
UniformFractureProcSettings.CollisionSampleSpacing = InUniformFractureSettings.CollisionSampleSpacing;
int ResultGeometryIndex = UE::Private::FractureHelpers::UniformFractureProc(
GeomCollection,
TransformSelectionArr,
UniformFractureProcSettings);
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
else
{
int ResultGeometryIndex = INDEX_NONE;
for (int32 TransformIndex : TransformSelectionArr)
{
TArray<int32> TransformSelection;
int32 Seed;
if (TransformToGeometryIndex[TransformIndex] > INDEX_NONE)
{
TransformSelection.Add(TransformIndex);
Seed = InUniformFractureSettings.RandomSeed + TransformIndex;
BoundingBox = BoundsToBone[TransformIndex];
// Fracture
FUniformFractureProcSettings UniformFractureProcSettings;
UniformFractureProcSettings.BBox = BoundingBox;
UniformFractureProcSettings.Transform = InUniformFractureSettings.Transform;
UniformFractureProcSettings.MinVoronoiSites = InUniformFractureSettings.MinVoronoiSites;
UniformFractureProcSettings.MaxVoronoiSites = InUniformFractureSettings.MaxVoronoiSites;
UniformFractureProcSettings.InternalMaterialID = InUniformFractureSettings.InternalMaterialID;
UniformFractureProcSettings.RandomSeed = Seed;
UniformFractureProcSettings.SplitIslands = InUniformFractureSettings.SplitIslands;
UniformFractureProcSettings.Grout = InUniformFractureSettings.Grout;
UniformFractureProcSettings.NoiseSettings = InUniformFractureSettings.NoiseSettings;
UniformFractureProcSettings.AddSamplesForCollision = InUniformFractureSettings.AddSamplesForCollision;
UniformFractureProcSettings.CollisionSampleSpacing = InUniformFractureSettings.CollisionSampleSpacing;
ResultGeometryIndex = UE::Private::FractureHelpers::UniformFractureProc(
GeomCollection,
TransformSelection,
UniformFractureProcSettings);
}
}
InOutCollection = (const FManagedArrayCollection&)(*GeomCollection);
return ResultGeometryIndex;
}
}
return INDEX_NONE;
}
namespace UE::Dataflow::Private
{
static void SetBoneColor(FManagedArrayCollection& InCollection, const int32 InBoneIdx, const FLinearColor InBoneColor)
{
const TManagedArray<TSet<int32>>& Children = InCollection.GetAttribute<TSet<int32>>(FTransformCollection::ChildrenAttribute, FGeometryCollection::TransformGroup);
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
if (Children[InBoneIdx].Num() == 0)
{
BoneColors[InBoneIdx] = InBoneColor;
return;
}
for (int32 Child : Children[InBoneIdx])
{
SetBoneColor(InCollection, Child, InBoneColor);
}
}
static FLinearColor GetRandomColor(const FRandomStream& InRandomStream, const int32 InColorRangeMin, const int32 InColorRangeMax)
{
const uint8 R = static_cast<uint8>(InRandomStream.FRandRange(InColorRangeMin, InColorRangeMax));
const uint8 G = static_cast<uint8>(InRandomStream.FRandRange(InColorRangeMin, InColorRangeMax));
const uint8 B = static_cast<uint8>(InRandomStream.FRandRange(InColorRangeMin, InColorRangeMax));
return FLinearColor(FColor(R, G, B, 255));
}
}
void FFractureEngineFracturing::InitColors(FManagedArrayCollection& InCollection)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
BoneColors[BoneIdx] = DataflowSettings->TransformLevelColors.BlankColor;
}
TManagedArray<FLinearColor>& VertexColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::ColorAttribute, FGeometryCollection::VerticesGroup);
const int32 NumVertices = VertexColors.Num();
for (int32 VertexIdx = 0; VertexIdx < NumVertices; ++VertexIdx)
{
VertexColors[VertexIdx] = DataflowSettings->TransformLevelColors.BlankColor;;
}
}
void FFractureEngineFracturing::TransferBoneColorToVertexColor(FManagedArrayCollection& InCollection)
{
const int32 NumTransforms = InCollection.NumElements(FGeometryCollection::TransformGroup);
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& TransformToGeometryIndexArr = InCollection.GetAttribute<int32>(FGeometryCollection::TransformToGeometryIndexAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& VertexStartArr = InCollection.GetAttribute<int32>(FGeometryCollection::VertexStartAttribute, FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexCountArr = InCollection.GetAttribute<int32>(FGeometryCollection::VertexCountAttribute, FGeometryCollection::GeometryGroup);
TManagedArray<FLinearColor>& VertexColorArr = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::ColorAttribute, FGeometryCollection::VerticesGroup);
for (int32 TransformIdx = 0; TransformIdx < NumTransforms; ++TransformIdx)
{
const int32 GeometryIndex = TransformToGeometryIndexArr[TransformIdx];
// Only transfer color to non clusters
if (GeometryIndex != -1)
{
const int32 VertexStart = VertexStartArr[GeometryIndex];
const int32 VertexCount = VertexCountArr[GeometryIndex];
for (int32 VertexIdx = VertexStart; VertexIdx < VertexStart + VertexCount; ++VertexIdx)
{
VertexColorArr[VertexIdx] = BoneColors[TransformIdx];
}
}
}
}
void FFractureEngineFracturing::SetBoneColorByParent(
FManagedArrayCollection& InCollection,
const FRandomStream& InRandomStream,
int32 InLevel,
const int32 InColorRangeMin,
const int32 InColorRangeMax)
{
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
const TManagedArray<int32>& Levels = InCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = InCollection.GetAttribute<TSet<int32>>(FTransformCollection::ChildrenAttribute, FGeometryCollection::TransformGroup);
if (InLevel == 0)
{
FLinearColor Color = UE::Dataflow::Private::GetRandomColor(InRandomStream, InColorRangeMin, InColorRangeMax);
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
BoneColors[BoneIdx] = Color;
}
}
else if (InLevel > 0)
{
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
if (Levels[BoneIdx] == InLevel - 1)
{
FLinearColor Color = UE::Dataflow::Private::GetRandomColor(InRandomStream, InColorRangeMin, InColorRangeMax);
for (int32 ChildBoneIdx : Children[BoneIdx])
{
UE::Dataflow::Private::SetBoneColor(InCollection, ChildBoneIdx, Color);
}
}
}
}
}
void FFractureEngineFracturing::SetBoneColorByLevel(FManagedArrayCollection& InCollection, int32 InLevel)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
const int32 NumTransformLevelColors = DataflowSettings->TransformLevelColors.LevelColors.Num();
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
const TManagedArray<int32>& Levels = InCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup);
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
if (InLevel >= 0)
{
if (Levels[BoneIdx] >= InLevel)
{
BoneColors[BoneIdx] = DataflowSettings->TransformLevelColors.LevelColors[InLevel % NumTransformLevelColors];
}
else
{
BoneColors[BoneIdx] = DataflowSettings->TransformLevelColors.BlankColor;
}
}
}
}
void FFractureEngineFracturing::SetBoneColorByCluster(
FManagedArrayCollection& InCollection,
const FRandomStream& InRandomStream,
int32 InLevel,
const int32 InColorRangeMin,
const int32 InColorRangeMax)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
TArray<FLinearColor> RandomColors;
FRandomStream Random(1);
for (int i = 0; i < 100; i++)
{
const uint8 R = static_cast<uint8>(Random.FRandRange(InColorRangeMin, InColorRangeMax));
const uint8 G = static_cast<uint8>(Random.FRandRange(InColorRangeMin, InColorRangeMax));
const uint8 B = static_cast<uint8>(Random.FRandRange(InColorRangeMin, InColorRangeMax));
RandomColors.Push(FLinearColor(FColor(R, G, B, 255)));
}
const TManagedArray<int32>& Parents = InCollection.GetAttribute<int32>("Parent", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& Levels = InCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup);
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumParents = Parents.Num();
for (int32 BoneIndex = 0, NumBones = NumParents; BoneIndex < NumBones; ++BoneIndex)
{
FLinearColor BoneColor = FLinearColor(FColor::Black);
if (Levels[BoneIndex] >= InLevel)
{
// go up until we find parent at the required ViewLevel
int32 Bone = BoneIndex;
while (Bone != -1 && Levels[Bone] > InLevel)
{
Bone = Parents[Bone];
}
int32 ColorIndex = Bone + 1; // parent can be -1 for root, range [-1..n]
BoneColor = RandomColors[ColorIndex % RandomColors.Num()];
BoneColor = BoneColor.LinearRGBToHSV();
BoneColor.B *= .5;
BoneColor = BoneColor.HSVToLinearRGB();
}
else
{
BoneColor = DataflowSettings->TransformLevelColors.BlankColor;;
}
BoneColors[BoneIndex] = BoneColor;
}
}
void FFractureEngineFracturing::SetBoneColorByLeafLevel(FManagedArrayCollection& InCollection, int32 InLevel)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
const int32 NumTransformLevelColors = DataflowSettings->TransformLevelColors.LevelColors.Num();
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
const TManagedArray<int32>& Levels = InCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = InCollection.GetAttribute<TSet<int32>>(FTransformCollection::ChildrenAttribute, FGeometryCollection::TransformGroup);
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
if (Levels[BoneIdx] >= InLevel)
{
BoneColors[BoneIdx] = Children[BoneIdx].Num() == 0 ? DataflowSettings->TransformLevelColors.LevelColors[Levels[BoneIdx] % NumTransformLevelColors] : FColor::Black;
}
else
{
BoneColors[BoneIdx] = DataflowSettings->TransformLevelColors.BlankColor;
}
}
}
void FFractureEngineFracturing::SetBoneColorByLeaf(FManagedArrayCollection& InCollection,
const FRandomStream& InRandomStream,
int32 InLevel,
const int32 InColorRangeMin,
const int32 InColorRangeMax)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const TManagedArray<int32>& Levels = InCollection.GetAttribute<int32>(FTransformCollection::LevelAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
for (int32 BoneIdx = 0; BoneIdx < NumBones; ++BoneIdx)
{
if (Levels[BoneIdx] >= InLevel)
{
BoneColors[BoneIdx] = UE::Dataflow::Private::GetRandomColor(InRandomStream, InColorRangeMin, InColorRangeMax);
}
else
{
BoneColors[BoneIdx] = DataflowSettings->TransformLevelColors.BlankColor;
}
}
}
void FFractureEngineFracturing::SetBoneColorByAttr(
FManagedArrayCollection& InCollection,
const FString InAttribute,
const float InMinAttrValue,
float InMaxAttrValue,
const FLinearColor InMinColor,
const FLinearColor InMaxColor)
{
const FName AttrName = FName(*InAttribute);
if (InCollection.HasAttribute(AttrName, FGeometryCollection::TransformGroup))
{
const TManagedArray<float>& AttrValues = InCollection.GetAttribute<float>(AttrName, FGeometryCollection::TransformGroup);
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
for (int32 Idx = 0; Idx < AttrValues.Num(); ++Idx)
{
float AttrValue = AttrValues[Idx];
// Make sure AttrValue is valid
AttrValue = FMath::Max(AttrValue, InMinAttrValue);
AttrValue = FMath::Min(AttrValue, InMaxAttrValue);
if (InMaxAttrValue < InMinAttrValue)
{
InMaxAttrValue = InMinAttrValue + 0.01f;
}
// Lerp between InMinColor and InMaxColor
float Alpha = (AttrValue - InMinAttrValue) / (InMaxAttrValue - InMinAttrValue);
FLinearColor NewColor = FLinearColor::LerpUsingHSV(InMinColor, InMaxColor, Alpha);
BoneColors[Idx] = NewColor;
}
}
}
void FFractureEngineFracturing::SetBoneColorRandom(
FManagedArrayCollection& InCollection,
const FRandomStream& InRandomStream)
{
TManagedArray<FLinearColor>& BoneColors = InCollection.ModifyAttribute<FLinearColor>(FGeometryCollection::BoneColorAttribute, FGeometryCollection::TransformGroup);
const int32 NumBones = BoneColors.Num();
for (int32 Idx = 0; Idx < NumBones; ++Idx)
{
const uint8 R = static_cast<uint8>(InRandomStream.FRandRange(5, 105));
const uint8 G = static_cast<uint8>(InRandomStream.FRandRange(5, 105));
const uint8 B = static_cast<uint8>(InRandomStream.FRandRange(5, 105));
BoneColors[Idx] = FLinearColor(FColor(R, G, B, 255));
}
}