// Fill out your copyright notice in the Description page of Project Settings. #include "MyTableIconActors.h" #include "KismetProceduralMeshLibrary.h" #include "TableIconActor.h" #include "Kismet/GameplayStatics.h" #include "Camera/CameraActor.h" ATableIconLineActor::ATableIconLineActor() { PrimaryActorTick.bCanEverTick = false; // purely driven by SetPoints USceneComponent* Root = CreateDefaultSubobject(TEXT("Root")); SetRootComponent(Root); MeshComponent = CreateDefaultSubobject(TEXT("LineMesh")); MeshComponent->SetupAttachment(RootComponent); MeshComponent->SetCastShadow(false); MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); } void ATableIconLineActor::SetPoints(const TArray& NewPoints) { const FTransform& ActorTransform = GetActorTransform(); Points.Reset(NewPoints.Num()); Points = NewPoints; MeshComponent->ClearMeshSection(0); BuildLineMesh(); if (LineMaterial) MeshComponent->SetMaterial(0, LineMaterial); } void ATableIconLineActor::BuildLineMesh() { TArray Vertices; TArray Triangles; TArray Normals; TArray UVs; TArray Colors; TArray Tangents; FVector Direction = FVector::ZeroVector; const float HalfLineThickness = LineThickness * 0.5f; for (int32 i = 0; i < Points.Num() - 1; i++) { const FVector& PointA = Points[i]; const FVector& PointB = Points[i + 1]; Direction = (PointB - PointA).GetSafeNormal(); const FVector Right = FVector::CrossProduct(FVector::UpVector, Direction).GetSafeNormal(); const FVector Offset = Right * (LineThickness * 0.5f); int32 Base = Vertices.Num(); Vertices.Add(PointA - Offset); Vertices.Add(PointA + Offset); Vertices.Add(PointB + Offset); Vertices.Add(PointB - Offset); Triangles.Add(Base + 0); Triangles.Add(Base + 1); Triangles.Add(Base + 2); Triangles.Add(Base + 0); Triangles.Add(Base + 2); Triangles.Add(Base + 3); } for (int i = 0; i < Vertices.Num(); i++) { Normals.Add(FVector::UpVector); } for (int i = 0; i < Vertices.Num(); i++) { UVs.Add(FVector2D::ZeroVector); } for (int i = 0; i < Vertices.Num(); i++) { Colors.Add(FColor::Red); } for (int i = 0; i < Vertices.Num(); i++) { Tangents.Add(FProcMeshTangent(Direction, false)); } MeshComponent->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, Colors, Tangents, false); } ATableIconWidgetActor::ATableIconWidgetActor() { USceneComponent* Root = CreateDefaultSubobject(TEXT("Root")); SetRootComponent(Root); PrimaryActorTick.bCanEverTick = true; WidgetComponent = CreateDefaultSubobject(TEXT("WidgetComponent")); WidgetComponent->SetupAttachment(RootComponent); WidgetComponent->SetDrawAtDesiredSize(true); WidgetComponent->SetWidgetSpace(EWidgetSpace::World); WidgetComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); WidgetInteraction = CreateDefaultSubobject(TEXT("WidgetInteraction")); WidgetInteraction->SetupAttachment(RootComponent); WidgetInteraction->InteractionSource = EWidgetInteractionSource::Custom; WidgetInteraction->InteractionDistance = 10.f; } void ATableIconWidgetActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); APlayerCameraManager* CamManager = UGameplayStatics::GetPlayerCameraManager(this, 0); if (!CamManager) return; FVector CamLocation = CamManager->GetCameraLocation(); FVector ToCamera = (CamLocation - GetActorLocation()).GetSafeNormal(); // Only rotate on yaw � keeps widget flat relative to table ToCamera.Z = 0.f; if (!ToCamera.IsNearlyZero()) SetActorRotation(ToCamera.ToOrientationRotator()); } void ATableIconWidgetActor::SetWidgetClass(TSubclassOf InWidgetClass) { if (!InWidgetClass) return; WidgetComponent->SetWidgetClass(InWidgetClass); WidgetComponent->InitWidget(); } UUserWidget* ATableIconWidgetActor::GetWidget() const { return WidgetComponent->GetUserWidgetObject(); } void ATableIconLabelActor::SetDistance(float RealWorldUnits, const FString& UnitSuffix) { int32 Snapped = FMath::RoundToInt(RealWorldUnits); SetLabelText(FString::Printf(TEXT("%d%s"), Snapped, *UnitSuffix)); }