// Copyright Epic Games, Inc. All Rights Reserved. #include "Dataflow/ChaosFleshComputeFiberFieldNode.h" #include "Chaos/Math/Poisson.h" #include "ChaosFlesh/ChaosFlesh.h" #include "ChaosFlesh/TetrahedralCollection.h" #include "GeometryCollection/Facades/CollectionMeshFacade.h" #include "GeometryCollection/Facades/CollectionMuscleActivationFacade.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ChaosFleshComputeFiberFieldNode) void FComputeFiberFieldNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { if (Out->IsA(&Collection)) { // // Gather inputs // if (!IsConnected(&Collection) || !IsConnected(&OriginIndices) || !IsConnected(&InsertionIndices)) { return; } FManagedArrayCollection InCollection = GetValue(Context, &Collection); TArray InOriginIndices = GetValue>(Context, &OriginIndices); TArray InInsertionIndices = GetValue>(Context, &InsertionIndices); // Tetrahedra TManagedArray* Elements = InCollection.FindAttribute( FTetrahedralCollection::TetrahedronAttribute, FTetrahedralCollection::TetrahedralGroup); if (!Elements) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *FTetrahedralCollection::TetrahedronAttribute.ToString(), *FTetrahedralCollection::TetrahedralGroup.ToString()); Out->SetValue(MoveTemp(InCollection), Context); return; } // Vertices TManagedArray* Vertex = InCollection.FindAttribute("Vertex", "Vertices"); if (!Vertex) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr 'Vertex' in group 'Vertices'")); Out->SetValue(MoveTemp(InCollection), Context); return; } // Incident elements TManagedArray>* IncidentElements = InCollection.FindAttribute>( FTetrahedralCollection::IncidentElementsAttribute, FGeometryCollection::VerticesGroup); if (!IncidentElements) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *FTetrahedralCollection::IncidentElementsAttribute.ToString(), *FGeometryCollection::VerticesGroup.ToString()); Out->SetValue(MoveTemp(InCollection), Context); return; } TManagedArray>* IncidentElementsLocalIndex = InCollection.FindAttribute>( FTetrahedralCollection::IncidentElementsLocalIndexAttribute, FGeometryCollection::VerticesGroup); if (!IncidentElementsLocalIndex) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *FTetrahedralCollection::IncidentElementsLocalIndexAttribute.ToString(), *FGeometryCollection::VerticesGroup.ToString()); Out->SetValue(MoveTemp(InCollection), Context); return; } // // Pull Origin & Insertion data out of the geometry collection. We may want other ways of specifying // these via an input on the node... // // Origin & Insertion TManagedArray* Origin = nullptr; TManagedArray* Insertion = nullptr; if (InOriginIndices.IsEmpty() || InInsertionIndices.IsEmpty()) { // Origin & Insertion group if (OriginInsertionGroupName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'OriginInsertionGroupName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } // Origin vertices if (InOriginIndices.IsEmpty()) { if (OriginVertexFieldName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'OriginVertexFieldName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } Origin = InCollection.FindAttribute(FName(OriginVertexFieldName), FName(OriginInsertionGroupName)); if (!Origin) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *OriginVertexFieldName, *OriginInsertionGroupName); Out->SetValue(MoveTemp(InCollection), Context); return; } else { InOriginIndices = Origin->GetConstArray(); } } // Insertion vertices if (InInsertionIndices.IsEmpty()) { if (InsertionVertexFieldName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'InsertionVertexFieldName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } Insertion = InCollection.FindAttribute(FName(InsertionVertexFieldName), FName(OriginInsertionGroupName)); if (!Insertion) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *InsertionVertexFieldName, *OriginInsertionGroupName); Out->SetValue(MoveTemp(InCollection), Context); return; } else { InInsertionIndices = Insertion->GetConstArray(); } } } // Only solve for fiber field on muscle geometries TSet MuscleGeometries; GeometryCollection::Facades::FCollectionMeshFacade MeshFacade(InCollection); TArray GeometryIndex = MeshFacade.GetGeometryGroupIndexArray(); for (int32 OriginIdx : InOriginIndices) { // Verify origins if (!Vertex->IsValidIndex(OriginIdx)) { Context.Error(FString::Printf( TEXT("OriginIdx %d is not a valid vertex index for vertex group size %d."), OriginIdx, Vertex->Num()), this); return; } if (GeometryIndex.IsValidIndex(OriginIdx)) { MuscleGeometries.Add(GeometryIndex[OriginIdx]); } } for (int32 InsertionIdx : InInsertionIndices) { // Verify insertions if (!Vertex->IsValidIndex(InsertionIdx)) { Context.Error(FString::Printf( TEXT("OriginIdx %d is not a valid vertex index for vertex group size %d."), InsertionIdx, Vertex->Num()), this); return; } if (GeometryIndex.IsValidIndex(InsertionIdx)) { MuscleGeometries.Add(GeometryIndex[InsertionIdx]); } } TArray MuscleElementIndices; TArray MuscleElements; TManagedArray* TetrahedronStart = InCollection.FindAttribute( FTetrahedralCollection::TetrahedronStartAttribute, FTetrahedralCollection::GeometryGroup); TManagedArray* TetrahedronCount = InCollection.FindAttribute( FTetrahedralCollection::TetrahedronCountAttribute, FTetrahedralCollection::GeometryGroup); TArray> MuscleConstraints; if (TetrahedronStart && TetrahedronCount) { for (int32 GeometryIdx : MuscleGeometries) { if (TetrahedronStart->IsValidIndex(GeometryIdx)) { for (int32 ElemIdx = (*TetrahedronStart)[GeometryIdx]; ElemIdx < (*TetrahedronStart)[GeometryIdx] + (*TetrahedronCount)[GeometryIdx]; ++ElemIdx) { MuscleElementIndices.Add(ElemIdx); MuscleElements.Add((*Elements)[ElemIdx]); } } } } MuscleConstraints.SetNum(MuscleElements.Num()); for (int32 LocalIdx = 0; LocalIdx < MuscleElements.Num(); ++LocalIdx) { for (int32 c = 0; c < 4; ++c) { MuscleConstraints[LocalIdx].Add(MuscleElements[LocalIdx][c]); } } TArray> MuscleIncidentElementsLocalIndex; TArray> MuscleIncidentElements = Chaos::Utilities::ComputeIncidentElements(MuscleConstraints, &MuscleIncidentElementsLocalIndex); // // Do the thing // TArray MuscleFiberDirs; TArray MuscleAttachmentScalarFieldTArray; //continuous field where origin = 1, insertion = 2, othernodes = 0 Chaos::ComputeFiberField( MuscleElements, Vertex->GetConstArray(), MuscleIncidentElements, MuscleIncidentElementsLocalIndex, InOriginIndices, InInsertionIndices, MuscleFiberDirs, MuscleAttachmentScalarFieldTArray, MaxIterations, Tolerance); // // Set output(s) // TManagedArray& FiberDirections = InCollection.AddAttribute("FiberDirection", FTetrahedralCollection::TetrahedralGroup); FiberDirections.Fill(FVector3f(0, 0, 0)); for (int32 LocalIdx = 0; LocalIdx < MuscleElements.Num(); ++LocalIdx) { FiberDirections[MuscleElementIndices[LocalIdx]] = MuscleFiberDirs[LocalIdx]; } if (bShowMuscleColor) { TManagedArray& Color = InCollection.AddAttribute("Color", FGeometryCollection::VerticesGroup); for (int32 i = 0; i < Color.Num(); ++i) { float Value = MuscleAttachmentScalarFieldTArray[i]; if (1 <= Value && Value <= 2) // 1 <= Value <= 2 if muscle with origin and insertion { Color[i] = FLinearColor(FVector(Value - 1, 0, 2 - Value)); } } } Out->SetValue(MoveTemp(InCollection), Context); } } TArray FComputeFiberFieldNode::GetNonZeroIndices(const TArray& Map) const { int32 NumNonZero = 0; for (int32 i = 0; i < Map.Num(); i++) if (Map[i]) NumNonZero++; TArray Indices; Indices.AddUninitialized(NumNonZero); int32 Idx = 0; for (int32 i = 0; i < Map.Num(); i++) if (Map[i]) Indices[Idx++] = i; return Indices; } void FComputeFiberStreamlineNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const { // // Gather inputs // FManagedArrayCollection InCollection = GetValue(Context, &Collection); FFieldCollection OutVectorField; TArray InOriginIndices = GetValue>(Context, &OriginIndices); TArray InInsertionIndices = GetValue>(Context, &InsertionIndices); // // Pull Origin & Insertion data out of the geometry collection. We may want other ways of specifying // these via an input on the node... // // Origin & Insertion TManagedArray* Origin = nullptr; TManagedArray* Insertion = nullptr; if (InOriginIndices.IsEmpty() || InInsertionIndices.IsEmpty()) { // Origin & Insertion group if (OriginInsertionGroupName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'OriginInsertionGroupName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } // Origin vertices if (InOriginIndices.IsEmpty()) { if (OriginVertexFieldName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'OriginVertexFieldName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } Origin = InCollection.FindAttribute(FName(OriginVertexFieldName), FName(OriginInsertionGroupName)); if (!Origin) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *OriginVertexFieldName, *OriginInsertionGroupName); Out->SetValue(MoveTemp(InCollection), Context); return; } } // Insertion vertices if (InInsertionIndices.IsEmpty()) { if (InsertionVertexFieldName.IsEmpty()) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Attr 'InsertionVertexFieldName' cannot be empty.")); Out->SetValue(MoveTemp(InCollection), Context); return; } Insertion = InCollection.FindAttribute(FName(InsertionVertexFieldName), FName(OriginInsertionGroupName)); if (!Insertion) { UE_LOG(LogChaosFlesh, Warning, TEXT("ComputeFiberFieldNode: Failed to find geometry collection attr '%s' in group '%s'"), *InsertionVertexFieldName, *OriginInsertionGroupName); Out->SetValue(MoveTemp(InCollection), Context); return; } } } InOriginIndices = Origin ? Origin->GetConstArray() : InOriginIndices; InInsertionIndices = Insertion ? Insertion->GetConstArray() : InInsertionIndices; if (InOriginIndices.Num() == 0 || InInsertionIndices.Num() == 0) { FindOutput(&VectorField)->SetValue(MoveTemp(OutVectorField), Context); FindOutput(&Collection)->SetValue(MoveTemp(InCollection), Context); return; } // // Compute muscle fiber streamlines // Save streamlines to muscle group // GeometryCollection::Facades::FMuscleActivationFacade MuscleActivation(InCollection); TArray>> Streamlines = MuscleActivation.BuildStreamlines(Origin ? Origin->GetConstArray() : InOriginIndices, Insertion ? Insertion->GetConstArray() : InInsertionIndices, NumLinesMultiplier, MaxStreamlineIterations, MaxPointsPerLine); //Render streamlines for (int32 i = 0; i < Streamlines.Num(); ++i) { for (int32 j = 0; j < Streamlines[i].Num(); ++j) { for (int32 k = 1; k < Streamlines[i][j].Num(); ++k) { OutVectorField.AddVectorToField(Streamlines[i][j][k - 1], Streamlines[i][j][k]); } } } // // Set output(s) // FindOutput(&VectorField)->SetValue(MoveTemp(OutVectorField), Context); FindOutput(&Collection)->SetValue(MoveTemp(InCollection), Context); }