// Copyright Epic Games, Inc. All Rights Reserved. #include "VisualLoggerRenderingActorBase.h" #include "DynamicMeshBuilder.h" #include "LogVisualizerSettings.h" #include "Materials/Material.h" #if WITH_EDITOR #include "GeomTools.h" #endif // WITH_EDITOR #include "VisualLoggerRenderingComponent.h" #include "VisualLogger/VisualLogger.h" #include "AI/Navigation/NavigationTypes.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(VisualLoggerRenderingActorBase) namespace FDebugDrawing { const FVector NavOffset(0., 0., 15.); } class UVisualLoggerRenderingComponent; class FVisualLoggerSceneProxy final : public FDebugRenderSceneProxy { public: virtual SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } FVisualLoggerSceneProxy(const UVisualLoggerRenderingComponent* InComponent) : FDebugRenderSceneProxy(InComponent) { DrawType = SolidAndWireMeshes; ViewFlagName = TEXT("VisLog"); ViewFlagIndex = uint32(FEngineShowFlags::FindIndexByName(*ViewFlagName)); bWantsSelectionOutline = false; FarClippingDistance = InComponent->GetFarClippingDistance(); } virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; Result.bDrawRelevance = IsShown(View); Result.bDynamicRelevance = true; // ideally the TranslucencyRelevance should be filled out by the material, here we do it conservative Result.bSeparateTranslucency = Result.bNormalTranslucency = IsShown(View) && GIsEditor; return Result; } virtual uint32 GetMemoryFootprint(void) const override { return sizeof(*this) + GetAllocatedSize(); } }; UVisualLoggerRenderingComponent::UVisualLoggerRenderingComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UVisualLoggerRenderingComponent::SetFarClippingDistance(double Distance) { if (FarClippingDistance != Distance) { FarClippingDistance = Distance; MarkRenderStateDirty(); } } FDebugRenderSceneProxy* UVisualLoggerRenderingComponent::CreateDebugSceneProxy() { AVisualLoggerRenderingActorBase* RenderingActor = Cast(GetOuter()); if (RenderingActor == nullptr) { return nullptr; } ULogVisualizerSettings *Settings = ULogVisualizerSettings::StaticClass()->GetDefaultObject(); FVisualLoggerSceneProxy *VLogSceneProxy = new FVisualLoggerSceneProxy(this); VLogSceneProxy->SolidMeshMaterial = Settings->GetDebugMeshMaterial(); RenderingActor->IterateDebugShapes([VLogSceneProxy] (const AVisualLoggerRenderingActorBase::FTimelineDebugShapes& Shapes) { VLogSceneProxy->Spheres.Append(Shapes.Points); VLogSceneProxy->Lines.Append(Shapes.Lines); VLogSceneProxy->Circles.Append(Shapes.Circles); VLogSceneProxy->Boxes.Append(Shapes.Boxes); VLogSceneProxy->Meshes.Append(Shapes.Meshes); VLogSceneProxy->Cones.Append(Shapes.Cones); VLogSceneProxy->Texts.Append(Shapes.Texts); VLogSceneProxy->Cylinders.Append(Shapes.Cylinders); VLogSceneProxy->ArrowLines.Append(Shapes.Arrows); VLogSceneProxy->Capsules.Append(Shapes.Capsules); VLogSceneProxy->CoordinateSystems.Append(Shapes.CoordinateSystems); }); return VLogSceneProxy; } FBoxSphereBounds UVisualLoggerRenderingComponent::CalcBounds(const FTransform& LocalToWorld) const { FBox MyBounds; MyBounds.Init(); MyBounds = FBox(FVector(-HALF_WORLD_MAX, -HALF_WORLD_MAX, -HALF_WORLD_MAX), FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX)); return MyBounds; } AVisualLoggerRenderingActorBase::AVisualLoggerRenderingActorBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { USceneComponent* SceneComponent = CreateDefaultSubobject(TEXT("SceneComp")); RootComponent = SceneComponent; RenderingComponent = CreateDefaultSubobject(TEXT("RenderingComponent")); } AVisualLoggerRenderingActorBase::~AVisualLoggerRenderingActorBase() { } void AVisualLoggerRenderingActorBase::SetFarClippingDistance(double Distance) { if (RenderingComponent != nullptr) { RenderingComponent->SetFarClippingDistance(Distance); } } double AVisualLoggerRenderingActorBase::GetFarClippingDistance() const { return (RenderingComponent != nullptr) ? RenderingComponent->GetFarClippingDistance() : 0.0; } namespace { static bool IsPolygonWindingCorrect(const TArray& Verts) { // this will work only for convex polys, but we're assuming that all logged polygons are convex in the first place if (Verts.Num() >= 3) { const FVector SurfaceNormal = FVector::CrossProduct(Verts[1] - Verts[0], Verts[2] - Verts[0]); const double TestDot = FVector::DotProduct(SurfaceNormal, FVector::UpVector); return TestDot > 0.; } return false; } static void GetPolygonMesh(const FVisualLogShapeElement* ElementToDraw, FDebugRenderSceneProxy::FMesh& TestMesh, const FVector3f& VertexOffset = FVector3f::ZeroVector) { TestMesh.Color = ElementToDraw->GetFColor(); FClipSMPolygon InPoly(ElementToDraw->Points.Num()); InPoly.FaceNormal = FVector::UpVector; const bool bHasCorrectWinding = IsPolygonWindingCorrect(ElementToDraw->Points); if (bHasCorrectWinding) { for (int32 Index = 0; Index < ElementToDraw->Points.Num(); Index++) { FClipSMVertex v1; v1.Pos = (FVector3f)ElementToDraw->Points[Index]; InPoly.Vertices.Add(v1); } } else { for (int32 Index = ElementToDraw->Points.Num() - 1; Index >= 0; Index--) { FClipSMVertex v1; v1.Pos = (FVector3f)ElementToDraw->Points[Index]; InPoly.Vertices.Add(v1); } } TArray OutTris; const bool bTriangulated = FGeomTools::TriangulatePoly(OutTris, InPoly, false); if (bTriangulated) { int32 LastIndex = 0; FGeomTools::RemoveRedundantTriangles(OutTris); for (const auto& CurrentTri : OutTris) { TestMesh.Vertices.Add(FDynamicMeshVertex(CurrentTri.Vertices[0].Pos + VertexOffset)); TestMesh.Vertices.Add(FDynamicMeshVertex(CurrentTri.Vertices[1].Pos + VertexOffset)); TestMesh.Vertices.Add(FDynamicMeshVertex(CurrentTri.Vertices[2].Pos + VertexOffset)); TestMesh.Indices.Add(LastIndex++); TestMesh.Indices.Add(LastIndex++); TestMesh.Indices.Add(LastIndex++); } } } } void AVisualLoggerRenderingActorBase::GetDebugShapes(const FVisualLogEntry& InEntry, bool bAddEntryLocationPointer, AVisualLoggerRenderingActorBase::FTimelineDebugShapes& DebugShapes) { const FVisualLogEntry* Entry = &InEntry; const FVisualLogShapeElement* ElementToDraw = Entry->ElementsToDraw.GetData(); const int32 ElementsCount = Entry->ElementsToDraw.Num(); if (bAddEntryLocationPointer) { constexpr float Length = 100; const FVector DirectionNorm = FVector::UpVector; FVector YAxis, ZAxis; DirectionNorm.FindBestAxisVectors(YAxis, ZAxis); DebugShapes.Cones.Add(FDebugRenderSceneProxy::FCone(FScaleMatrix(FVector(Length)) * FMatrix(DirectionNorm, YAxis, ZAxis, Entry->Location), 5, 5, FColor::Red)); } if (DebugShapes.LogEntriesPath.Num()) { FVector Location = DebugShapes.LogEntriesPath[0]; for (int32 Index = 1; Index < DebugShapes.LogEntriesPath.Num(); ++Index) { const FVector CurrentLocation = DebugShapes.LogEntriesPath[Index]; DebugShapes.Lines.Add(FDebugRenderSceneProxy::FDebugLine(Location, CurrentLocation, FColor(160, 160, 240), 2.0)); Location = CurrentLocation; } } for (int32 ElementIndex = 0; ElementIndex < ElementsCount; ++ElementIndex, ++ElementToDraw) { if (!MatchCategoryFilters(ElementToDraw->Category, ElementToDraw->Verbosity)) { continue; } const FVector3f CorridorOffset = (FVector3f)FDebugDrawing::NavOffset * 1.25f; const FColor Color = ElementToDraw->GetFColor(); const EVisualLoggerShapeElement ElementType = ElementToDraw->GetType(); // The wire elements force the draw type : const FDebugRenderSceneProxy::EDrawType DrawTypeOverride = ( (ElementType == EVisualLoggerShapeElement::WireBox) || (ElementType == EVisualLoggerShapeElement::WireSphere) || (ElementType == EVisualLoggerShapeElement::WireCapsule) || (ElementType == EVisualLoggerShapeElement::WireCone) || (ElementType == EVisualLoggerShapeElement::WireCylinder) || (ElementType == EVisualLoggerShapeElement::WireCircle)) ? FDebugRenderSceneProxy::EDrawType::WireMesh : FDebugRenderSceneProxy::EDrawType::Invalid; switch (ElementType) { case EVisualLoggerShapeElement::SinglePoint: case EVisualLoggerShapeElement::Sphere: case EVisualLoggerShapeElement::WireSphere: { const float Radius = float(ElementToDraw->Radius); const bool bDrawLabel = (ElementToDraw->Description.IsEmpty() == false); const int32 NumPoints = ElementToDraw->Points.Num(); for (int32 Index = 0; Index < NumPoints; ++Index) { const FVector& Point = ElementToDraw->Points[Index]; DebugShapes.Points.Add(FDebugRenderSceneProxy::FSphere(Radius, Point, Color, DrawTypeOverride)); if (bDrawLabel) { const FString PrintString = NumPoints == 1 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, Point, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Polygon: { FDebugRenderSceneProxy::FMesh TestMesh; GetPolygonMesh(ElementToDraw, TestMesh, CorridorOffset); DebugShapes.Meshes.Add(TestMesh); for (int32 VIdx = 0; VIdx < ElementToDraw->Points.Num(); VIdx++) { DebugShapes.Lines.Add(FDebugRenderSceneProxy::FDebugLine( ElementToDraw->Points[VIdx] + (FVector)CorridorOffset, ElementToDraw->Points[(VIdx + 1) % ElementToDraw->Points.Num()] + (FVector)CorridorOffset, FColor::Cyan, 2) ); } } break; case EVisualLoggerShapeElement::Mesh: { struct FHeaderData { int32 VerticesNum, FacesNum; FHeaderData(const FVector& InVector) : VerticesNum(static_cast(InVector.X)), FacesNum(static_cast(InVector.Y)) {} }; const FHeaderData HeaderData(ElementToDraw->Points[0]); FDebugRenderSceneProxy::FMesh TestMesh; TestMesh.Color = ElementToDraw->GetFColor(); int32 StartIndex = 1; int32 EndIndex = StartIndex + HeaderData.VerticesNum; for (int32 VIdx = StartIndex; VIdx < EndIndex; VIdx++) { const FVector& CurrentPoint = ElementToDraw->Points[VIdx]; TestMesh.Vertices.Add(FVector3f(CurrentPoint)); TestMesh.Box += CurrentPoint; } StartIndex = EndIndex; EndIndex = StartIndex + HeaderData.FacesNum; for (int32 VIdx = StartIndex; VIdx < EndIndex; VIdx++) { const FVector &CurrentFace = ElementToDraw->Points[VIdx]; TestMesh.Indices.Add(static_cast(CurrentFace.X)); TestMesh.Indices.Add(static_cast(CurrentFace.Y)); TestMesh.Indices.Add(static_cast(CurrentFace.Z)); } DebugShapes.Meshes.Add(TestMesh); } break; case EVisualLoggerShapeElement::Segment: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = (ElementToDraw->Description.IsEmpty() == false); const FVector* Location = ElementToDraw->Points.GetData(); const int32 NumPoints = ElementToDraw->Points.Num(); for (int32 Index = 0; Index + 1 < NumPoints; Index += 2, Location += 2) { DebugShapes.Lines.Add(FDebugRenderSceneProxy::FDebugLine(*Location, *(Location + 1), Color, Thickness)); if (bDrawLabel) { const FString PrintString = NumPoints == 2 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index / 2); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, (*Location + (*(Location + 1) - *Location) / 2), Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Path: { const float Thickness = float(ElementToDraw->Thickness); FVector Location = ElementToDraw->Points[0]; for (int32 Index = 1; Index < ElementToDraw->Points.Num(); ++Index) { const FVector CurrentLocation = ElementToDraw->Points[Index]; DebugShapes.Lines.Add(FDebugRenderSceneProxy::FDebugLine(Location, CurrentLocation, Color, Thickness)); Location = CurrentLocation; } } break; case EVisualLoggerShapeElement::Box: case EVisualLoggerShapeElement::WireBox: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = (ElementToDraw->Description.IsEmpty() == false); const FVector* BoxExtent = ElementToDraw->Points.GetData(); const int32 NumPoints = ElementToDraw->Points.Num(); FTransform Transform(ElementToDraw->TransformationMatrix); for (int32 Index = 0; Index + 1 < NumPoints; Index += 2, BoxExtent += 2) { const FBox Box = FBox(*BoxExtent, *(BoxExtent + 1)); DebugShapes.Boxes.Add(FDebugRenderSceneProxy::FDebugBox(Box, Color, Transform, DrawTypeOverride, Thickness)); if (bDrawLabel) { const FString PrintString = NumPoints == 2 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index / 2); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, Transform.TransformPosition(Box.GetCenter()), Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Cone: case EVisualLoggerShapeElement::WireCone: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false; for (int32 Index = 0; Index + 2 < ElementToDraw->Points.Num(); Index += 3) { const FVector Origin = ElementToDraw->Points[Index]; const FVector Direction = ElementToDraw->Points[Index + 1].GetSafeNormal(); const FVector Angles = ElementToDraw->Points[Index + 2]; const double Length = Angles.X; FVector YAxis, ZAxis; Direction.FindBestAxisVectors(YAxis, ZAxis); DebugShapes.Cones.Add(FDebugRenderSceneProxy::FCone(FScaleMatrix(FVector(Length)) * FMatrix(Direction, YAxis, ZAxis, Origin), static_cast(Angles.Y), static_cast(Angles.Z), Color, DrawTypeOverride, Thickness)); if (bDrawLabel) { DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(ElementToDraw->Description, Origin, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Cylinder: case EVisualLoggerShapeElement::WireCylinder: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false; for (int32 Index = 0; Index + 2 < ElementToDraw->Points.Num(); Index += 3) { const FVector Start = ElementToDraw->Points[Index]; const FVector End = ElementToDraw->Points[Index + 1]; const FVector OtherData = ElementToDraw->Points[Index + 2]; const float HalfHeight = FloatCastChecked(0.5 * (End - Start).Size(), UE::LWC::DefaultFloatPrecision); const FVector Center = 0.5 * (Start + End); DebugShapes.Cylinders.Add(FDebugRenderSceneProxy::FWireCylinder(Center , (End - Start).GetSafeNormal() , static_cast(OtherData.X) , HalfHeight, Color, DrawTypeOverride, Thickness)); // Base parameter is the center of the cylinder if (bDrawLabel) { DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(ElementToDraw->Description, Center, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Capsule: case EVisualLoggerShapeElement::WireCapsule: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false; for (int32 Index = 0; Index + 2 < ElementToDraw->Points.Num(); Index += 3) { const FVector Base = ElementToDraw->Points[Index + 0]; const FVector FirstData = ElementToDraw->Points[Index + 1]; const FVector SecondData = ElementToDraw->Points[Index + 2]; const float HalfHeight = static_cast(FirstData.X); const float Radius = static_cast(FirstData.Y); const FQuat Rotation = FQuat(FirstData.Z, SecondData.X, SecondData.Y, SecondData.Z); const FMatrix Axes = FQuatRotationTranslationMatrix(Rotation, FVector::ZeroVector); const FVector XAxis = Axes.GetScaledAxis(EAxis::X); const FVector YAxis = Axes.GetScaledAxis(EAxis::Y); const FVector ZAxis = Axes.GetScaledAxis(EAxis::Z); DebugShapes.Capsules.Add(FDebugRenderSceneProxy::FCapsule(Base, Radius, XAxis, YAxis, ZAxis, HalfHeight, Color, DrawTypeOverride, Thickness)); if (bDrawLabel) { DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(ElementToDraw->Description, Base + HalfHeight * FVector::UpVector, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::NavAreaMesh: { if (ElementToDraw->Points.Num() == 0) { continue; } struct FHeaderData { float MinZ, MaxZ; FHeaderData(const FVector& InVector) : MinZ(FloatCastChecked(InVector.X, UE::LWC::DefaultFloatPrecision)), MaxZ(FloatCastChecked(InVector.Y, UE::LWC::DefaultFloatPrecision)) {} }; const FHeaderData HeaderData(ElementToDraw->Points[0]); TArray AreaMeshPoints = ElementToDraw->Points; AreaMeshPoints.RemoveAt(0, EAllowShrinking::No); AreaMeshPoints.Add(ElementToDraw->Points[1]); TArray Vertices; TNavStatArray Faces; int32 CurrentIndex = 0; FDebugRenderSceneProxy::FMesh TestMesh; TestMesh.Color = ElementToDraw->GetFColor(); TestMesh.Vertices.Reserve((AreaMeshPoints.Num() - 1) * 6); TestMesh.Indices.Reserve((AreaMeshPoints.Num() - 1) * 6); for (int32 PointIndex = 0; PointIndex < AreaMeshPoints.Num() - 1; PointIndex++) { FVector Point = AreaMeshPoints[PointIndex]; FVector NextPoint = AreaMeshPoints[PointIndex + 1]; // LWC_TODO These won't work well with very large coordinates! FVector3f P1(UE::LWC::NarrowWorldPositionChecked(Point.X, Point.Y, HeaderData.MinZ)); FVector3f P2(UE::LWC::NarrowWorldPositionChecked(Point.X, Point.Y, HeaderData.MaxZ)); FVector3f P3(UE::LWC::NarrowWorldPositionChecked(NextPoint.X, NextPoint.Y, HeaderData.MinZ)); FVector3f P4(UE::LWC::NarrowWorldPositionChecked(NextPoint.X, NextPoint.Y, HeaderData.MaxZ)); TestMesh.Vertices.Add(P1); TestMesh.Vertices.Add(P2); TestMesh.Vertices.Add(P3); TestMesh.Indices.Add(CurrentIndex++); TestMesh.Indices.Add(CurrentIndex++); TestMesh.Indices.Add(CurrentIndex++); TestMesh.Vertices.Add(P3); TestMesh.Vertices.Add(P2); TestMesh.Vertices.Add(P4); TestMesh.Indices.Add(CurrentIndex++); TestMesh.Indices.Add(CurrentIndex++); TestMesh.Indices.Add(CurrentIndex++); } DebugShapes.Meshes.Add(TestMesh); { FDebugRenderSceneProxy::FMesh PolygonMesh; FVisualLogShapeElement PolygonToDraw(EVisualLoggerShapeElement::Polygon); PolygonToDraw.SetColor(ElementToDraw->GetFColor()); PolygonToDraw.Points.Reserve(AreaMeshPoints.Num()); PolygonToDraw.Points = AreaMeshPoints; GetPolygonMesh(&PolygonToDraw, PolygonMesh, FVector3f(0., 0., HeaderData.MaxZ)); DebugShapes.Meshes.Add(PolygonMesh); } for (int32 VIdx = 0; VIdx < AreaMeshPoints.Num(); VIdx++) { DebugShapes.Lines.Add(FDebugRenderSceneProxy::FDebugLine( AreaMeshPoints[VIdx] + FVector(0., 0., HeaderData.MaxZ), AreaMeshPoints[(VIdx + 1) % AreaMeshPoints.Num()] + FVector(0., 0., HeaderData.MaxZ), ElementToDraw->GetFColor(), 2) ); } } break; case EVisualLoggerShapeElement::Arrow: { const bool bDrawLabel = (ElementToDraw->Description.IsEmpty() == false); const FVector* Location = ElementToDraw->Points.GetData(); const int32 NumPoints = ElementToDraw->Points.Num(); const float Mag = float(ElementToDraw->Mag); for (int32 Index = 0; Index + 1 < NumPoints; Index += 2, Location += 2) { DebugShapes.Arrows.Add(FDebugRenderSceneProxy::FArrowLine(*Location, *(Location + 1), Color, Mag)); if (bDrawLabel) { const FString PrintString = NumPoints == 2 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index / 2); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, (*Location + (*(Location + 1) - *Location) / 2), Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Circle: case EVisualLoggerShapeElement::WireCircle: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false; const int32 NumPoints = ElementToDraw->Points.Num(); for (int32 Index = 0; Index + 2 < NumPoints; Index += 3) { const FVector Center = ElementToDraw->Points[Index + 0]; const FVector UpAxis = ElementToDraw->Points[Index + 1]; const double Radius = ElementToDraw->Points[Index + 2].X; DebugShapes.Circles.Add(FDebugRenderSceneProxy::FCircle(Center, UpAxis.GetSafeNormal(), Radius, Color, DrawTypeOverride, Thickness)); if (bDrawLabel) { const FString PrintString = NumPoints == 3 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index / 3); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, Center, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::CoordinateSystem: { const float Thickness = float(ElementToDraw->Thickness); const bool bDrawLabel = ElementToDraw->Description.IsEmpty() == false; const int32 NumPoints = ElementToDraw->Points.Num(); for (int32 Index = 0; Index + 2 < NumPoints; Index += 3) { const FVector AxisLoc = ElementToDraw->Points[Index]; const FRotator AxisRot = ElementToDraw->Points[Index + 1].ToOrientationRotator(); const float Scale = ElementToDraw->Points[Index + 2].X; DebugShapes.CoordinateSystems.Add(FDebugRenderSceneProxy::FCoordinateSystem(AxisLoc, AxisRot, Scale, Color, Thickness)); if (bDrawLabel) { const FString PrintString = NumPoints == 3 ? ElementToDraw->Description : FString::Printf(TEXT("%s_%d"), *ElementToDraw->Description, Index / 3); DebugShapes.Texts.Add(FDebugRenderSceneProxy::FText3d(PrintString, AxisLoc, Color.WithAlpha(255))); } } } break; case EVisualLoggerShapeElement::Invalid: UE_LOG(LogVisual, Warning, TEXT("Invalid element type")); break; default: UE_LOG(LogVisual, Warning, TEXT("Unhandled element type: %d"), int(ElementToDraw->GetType())); } } }