// Copyright Epic Games, Inc. All Rights Reserved. #include "DataflowRendering.h" #include "PhysicsEngine/AggregateGeom.h" #include "PhysicsEngine/BoxElem.h" #include "GeometryCollection/Facades/CollectionRenderingFacade.h" #include "Engine/SkeletalMesh.h" #include "ReferenceSkeleton.h" #include "PhysicsEngine/SkeletalBodySetup.h" #include "BoneSelection.h" #include "PhysicsAssetDataflowState.h" namespace UE::Chaos::RigidAsset { void RenderAggGeom(GeometryCollection::Facades::FRenderingFacade& RenderingFacade, const UE::Dataflow::FGraphRenderingState& State, const FKAggregateGeom& AggGeom, const FTransform& BaseTm, FName BoneName, const FLinearColor& InColor) { const int32 NumElems = AggGeom.GetElementCount(); if(NumElems == 0) { // Nothing to actually draw - return before opening a geometry group return; } for(int32 Index = 0; Index < NumElems; ++Index) { FString GeometryName = FString::Printf(TEXT("%s_%s_%d"), *State.GetGuid().ToString(), *BoneName.ToString(), Index); const int32 GeometryGroup = RenderingFacade.StartGeometryGroup(GeometryName); const FKShapeElem* Elem = AggGeom.GetElement(Index); if(!Elem) { break; } switch(Elem->GetShapeType()) { case EAggCollisionShape::Box: { const FKBoxElem* AsBox = static_cast(Elem); const FVector3f Extent = FVector3f{ AsBox->X, AsBox->Y, AsBox->Z } *0.5f; RenderingFacade.SetGroupTransform(GeometryGroup, Elem->GetTransform() * BaseTm); RenderingFacade.AddBox({ -Extent, Extent }); break; } case EAggCollisionShape::Sphere: { const FKSphereElem* AsSphere = static_cast(Elem); // Transform placed in the group - always draw at (0,0,0) RenderingFacade.SetGroupTransform(GeometryGroup, Elem->GetTransform() * BaseTm); RenderingFacade.AddSphere({0.0f, 0.0f, 0.0f}, AsSphere->Radius, InColor); break; } case EAggCollisionShape::Sphyl: { const FKSphylElem* AsSphyl = static_cast(Elem); // AddCapsule uses the capsule mesh generator which has the origin of the capsule at one end of // the segment, we need to transform first to the centre of the capsule, then apply the elem transform const FTransform CapsuleCollisionAdjust(FQuat::Identity, FVector(0.0f, 0.0f, -AsSphyl->Length / 2)); RenderingFacade.SetGroupTransform(GeometryGroup, CapsuleCollisionAdjust * Elem->GetTransform() * BaseTm); RenderingFacade.AddCapsule(AsSphyl->Length, AsSphyl->Radius); break; } case EAggCollisionShape::Convex: { const FKConvexElem* AsConvex = static_cast(Elem); const int32 NumIndices = AsConvex->IndexData.Num(); if(NumIndices == 0 || NumIndices % 3 != 0) { // Empty or invalid convex index data. break; } RenderingFacade.SetGroupTransform(GeometryGroup, Elem->GetTransform() * BaseTm); const int32 NumFaces = NumIndices / 3; TArray Positions; Positions.Reserve(AsConvex->VertexData.Num()); for(const FVector& Position : AsConvex->VertexData) { Positions.Add(static_cast(Position)); } TArray Indices; Indices.Reserve(NumFaces); for(int32 FaceIndex = 0; FaceIndex < AsConvex->IndexData.Num(); FaceIndex += 3) { Indices.Add({ AsConvex->IndexData[FaceIndex + 0], AsConvex->IndexData[FaceIndex + 2], AsConvex->IndexData[FaceIndex + 1] }); } TArray Colors; Colors.SetNum(AsConvex->VertexData.Num()); for(FLinearColor& Color : Colors) { Color = FLinearColor::White; } RenderingFacade.AddFaces(Positions, Indices, Colors); break; } default: break; } RenderingFacade.EndGeometryGroup(GeometryGroup); } } UE::Dataflow::FRenderKey FAggregateGeometryGeomRenderCallbacks::StaticGetRenderKey() { return { "GeomRender", FName("FKAggregateGeom") }; } UE::Dataflow::FRenderKey FAggregateGeometryGeomRenderCallbacks::GetRenderKey() const { return StaticGetRenderKey(); } bool FAggregateGeometryGeomRenderCallbacks::CanRender(const UE::Dataflow::IDataflowConstructionViewMode& ViewMode) const { return true; } void FAggregateGeometryGeomRenderCallbacks::Render(GeometryCollection::Facades::FRenderingFacade& RenderingFacade, const UE::Dataflow::FGraphRenderingState& State) { if(State.GetRenderOutputs().Num() > 0) { FKAggregateGeom Default; const FKAggregateGeom& AggGeom = State.GetValue(State.GetRenderOutputs()[0], Default); RenderAggGeom(RenderingFacade, State, AggGeom, FTransform::Identity, NAME_None, FLinearColor::White); } } UE::Dataflow::FRenderKey FBoneSelectionRenderCallbacks::StaticGetRenderKey() { return { "GeomRender", FName("FBoneSelection") }; } UE::Dataflow::FRenderKey FBoneSelectionRenderCallbacks::GetRenderKey() const { return StaticGetRenderKey(); } bool FBoneSelectionRenderCallbacks::CanRender(const UE::Dataflow::IDataflowConstructionViewMode& ViewMode) const { return true; } // GeometryCore perp vectors impl, not currently exposed in that module template void MakePerpVectors(const UE::Math::TVector& Normal, UE::Math::TVector& OutPerp1, UE::Math::TVector& OutPerp2) { // Duff et al method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf if(Normal.Z < (RealType)0) { RealType A = (RealType)1 / ((RealType)1 - Normal.Z); RealType B = Normal.X * Normal.Y * A; OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; OutPerp1.Y = -B; OutPerp1.Z = Normal.X; OutPerp2.X = B; OutPerp2.Y = Normal.Y * Normal.Y * A - (RealType)1; OutPerp2.Z = -Normal.Y; } else { RealType A = (RealType)1 / ((RealType)1 + Normal.Z); RealType B = -Normal.X * Normal.Y * A; OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; OutPerp1.Y = B; OutPerp1.Z = -Normal.X; OutPerp2.X = B; OutPerp2.Y = (RealType)1 - Normal.Y * Normal.Y * A; OutPerp2.Z = -Normal.Y; } } void DrawBone(GeometryCollection::Facades::FRenderingFacade& RenderingFacade, const FVector3f& From, const FVector3f& To, const FLinearColor& InColor) { float Length; FVector3f Axes[3]; const FVector3f Segment = To - From; Segment.ToDirectionAndLength(Axes[2], Length); MakePerpVectors(Axes[2], Axes[0], Axes[1]); constexpr float WidePoint = 0.3f; constexpr float Thickness = 2.5f; TArray Verts; TArray Colors; Verts.SetNumUninitialized(6); Colors.SetNumUninitialized(6); Verts[0] = From; Verts[5] = To; Verts[1] = From + (Axes[0] + Axes[1]) * Thickness + Axes[2] * (WidePoint * Length); Verts[2] = From + (Axes[0] + -Axes[1]) * Thickness + Axes[2] * (WidePoint * Length); Verts[3] = From + (-Axes[0] + -Axes[1]) * Thickness + Axes[2] * (WidePoint * Length); Verts[4] = From + (-Axes[0] + Axes[1]) * Thickness + Axes[2] * (WidePoint * Length); TArray Indices; Indices.SetNumUninitialized(8); // Top Section Indices[0] = FIntVector{ 0, 2, 1 }; Indices[1] = FIntVector{ 0, 3, 2 }; Indices[2] = FIntVector{ 0, 4, 3 }; Indices[3] = FIntVector{ 0, 1, 4 }; // Bottom Section Indices[4] = FIntVector{ 5, 1, 2 }; Indices[5] = FIntVector{ 5, 2, 3 }; Indices[6] = FIntVector{ 5, 3, 4 }; Indices[7] = FIntVector{ 5, 4, 1 }; for(FLinearColor& Color : Colors) { Color = InColor; } RenderingFacade.AddFaces(Verts, Indices, Colors); } void FBoneSelectionRenderCallbacks::Render(GeometryCollection::Facades::FRenderingFacade& RenderingFacade, const UE::Dataflow::FGraphRenderingState& State) { if(State.GetRenderOutputs().Num() > 0) { FRigidAssetBoneSelection Default; const FRigidAssetBoneSelection& Selection = State.GetValue(State.GetRenderOutputs()[0], Default); if(!Selection.Mesh || !Selection.Skeleton) { return; } const FReferenceSkeleton& RefSkel = Selection.Mesh->GetRefSkeleton(); TArray BoneTransforms; TArray ParentIndices; TArray SelectedIndices; RefSkel.GetBoneAbsoluteTransforms(BoneTransforms); const int32 NumBones = BoneTransforms.Num(); SelectedIndices.SetNumZeroed(NumBones); for(const FRigidAssetBoneInfo& Selected : Selection.SelectedBones) { SelectedIndices[Selected.Index] = true; } ParentIndices.SetNum(NumBones); for(int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { ParentIndices[BoneIndex] = RefSkel.GetParentIndex(BoneIndex); } int32 Group = RenderingFacade.StartGeometryGroup(TEXT("BoneSelection")); for(int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { const FTransform& BoneTransform = BoneTransforms[BoneIndex]; RenderingFacade.AddPoint((FVector3f)BoneTransform.GetTranslation()); const int32 ParentIndex = ParentIndices[BoneIndex]; if(ParentIndex != INDEX_NONE) { const FLinearColor BoneColor = SelectedIndices[ParentIndex] ? FLinearColor(1.0f, 0.6f, 0.0f) : FLinearColor::Gray; DrawBone(RenderingFacade, (FVector3f)BoneTransforms[ParentIndex].GetTranslation(), (FVector3f)BoneTransforms[BoneIndex].GetTranslation(), BoneColor); } } RenderingFacade.EndGeometryGroup(Group); } } UE::Dataflow::FRenderKey FPhysAssetStateRenderCallbacks::StaticGetRenderKey() { return { "GeomRender", FName("FPhysicsAssetDataflowState") }; } UE::Dataflow::FRenderKey FPhysAssetStateRenderCallbacks::GetRenderKey() const { return StaticGetRenderKey(); } bool FPhysAssetStateRenderCallbacks::CanRender(const UE::Dataflow::IDataflowConstructionViewMode& ViewMode) const { return true; } void FPhysAssetStateRenderCallbacks::Render(GeometryCollection::Facades::FRenderingFacade& RenderingFacade, const UE::Dataflow::FGraphRenderingState& State) { if(State.GetRenderOutputs().Num() > 0) { FPhysicsAssetDataflowState Default; const FPhysicsAssetDataflowState& AssetState = State.GetValue(State.GetRenderOutputs()[0], Default); if(AssetState.HasData()) { const FReferenceSkeleton& RefSkeleton = AssetState.TargetMesh->GetRefSkeleton(); for(TObjectPtr Body : AssetState.Bodies) { const int32 BoneIndex = RefSkeleton.FindBoneIndex(Body->BoneName); if(BoneIndex != INDEX_NONE) { FTransform BoneTransform = RefSkeleton.GetBoneAbsoluteTransform(BoneIndex); RenderAggGeom(RenderingFacade, State, Body->AggGeom, BoneTransform, Body->BoneName, FLinearColor(1.0f, 1.0f, 1.0f, 0.5f)); } } } } } }