Giant_Beast_2025/Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitBlobShadowComponent.cpp

146 lines
5.3 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitBlobShadowComponent.h"
#include "MRUtilityKitTelemetry.h"
#include "MRUtilityKit.h"
#include "Kismet/KismetSystemLibrary.h"
#include "UObject/ConstructorHelpers.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Engine/StaticMesh.h"
UMRUKBlobShadowComponent::UMRUKBlobShadowComponent()
{
const ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneAsset(TEXT("/Engine/BasicShapes/Plane"));
if (PlaneAsset.Succeeded())
{
SetStaticMesh(PlaneAsset.Object);
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find plane mesh in /Engine/BasicShapes/Plane"));
}
const ConstructorHelpers::FObjectFinder<UMaterialInstance> BlobShadowMaterialAsset(TEXT("/OculusXR/Materials/MI_BlobShadow"));
if (BlobShadowMaterialAsset.Succeeded())
{
SetMaterial(0, BlobShadowMaterialAsset.Object);
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find blob shadow material in /OculusXR/Materials/MI_BlobShadow"));
}
// Prevent sorting issue with transparent ground
SetTranslucentSortPriority(1);
// We don't want any collision
SetCollisionProfileName("NoCollision");
// Need tick to be enabled
SetComponentTickEnabled(true);
PrimaryComponentTick.bCanEverTick = true;
bAutoActivate = true;
}
void UMRUKBlobShadowComponent::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadBlobShadowMarker> Event(static_cast<int>(GetTypeHash(this)));
// Create dynamic material (for roundness and gradient settings)
DynMaterial = CreateAndSetMaterialInstanceDynamic(0);
// Since we're updating the component size and position every frame it's better to not be influenced by parent
SetUsingAbsoluteLocation(true);
SetUsingAbsoluteRotation(true);
SetUsingAbsoluteScale(true);
// Compute size and position once
UpdatePlaneSizeAndPosition();
}
void UMRUKBlobShadowComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Update component size and position every frame
UpdatePlaneSizeAndPosition();
}
void UMRUKBlobShadowComponent::UpdatePlaneSizeAndPosition()
{
FVector Origin;
FVector2D Extent;
double Yaw;
ComputeOwner2DBounds(Origin, Extent, Yaw);
Extent += FVector2D::UnitVector * ExtraExtent; // Additional extent
SetWorldScale3D(FVector(Extent * 0.02f, 1.f)); // Plane mesh is 100x100, multiplying by 0.02f to match the correct size when scaling
SetWorldRotation(FRotator(0.f, Yaw, 0.f));
// Sphere trace to the ground
FHitResult Hit;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(GetOwner());
const bool bHasHit = UKismetSystemLibrary::SphereTraceSingle(this, Origin, Origin + FVector::DownVector * MaxVerticalDistance, Extent.Length() * 0.5f, TraceTypeQuery1,
true, ActorsToIgnore, EDrawDebugTrace::None, Hit, true);
float Opacity = 0.f;
if (bHasHit)
{
SetHiddenInGame(false); // Make plane visible
SetWorldLocation(Hit.ImpactPoint + FVector::UpVector * 0.02f); // Impact + some offset to avoid Z-fighting
Opacity = FMath::GetMappedRangeValueClamped(
FVector2D(MaxVerticalDistance - FadeDistance, MaxVerticalDistance),
FVector2D(1.f, 0.f),
Hit.Distance); // Set opacity based on distance to ground
}
else
SetHiddenInGame(true); // Hide plane
// Update material's parameters
if (DynMaterial)
{
DynMaterial->SetScalarParameterValue("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
DynMaterial->SetScalarParameterValue("Gradient", Gradient);
DynMaterial->SetScalarParameterValue("GradientPower", GradientPower);
DynMaterial->SetScalarParameterValue("Opacity", Opacity);
}
else // In case DynMaterial doesn't exist (e.g. in editor), update values directly on the mesh
{
SetScalarParameterValueOnMaterials("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
SetScalarParameterValueOnMaterials("Gradient", Gradient);
SetScalarParameterValueOnMaterials("GradientPower", GradientPower);
SetScalarParameterValueOnMaterials("Opacity", Opacity);
}
}
void UMRUKBlobShadowComponent::ComputeOwner2DBounds(FVector& Origin, FVector2D& Extent, double& Yaw) const
{
const AActor* Actor = GetOwner();
// Calculate local space BoundingBox from all components, but keep yaw to have a correct 2D bounding box at the end
FBox Box(ForceInit);
const FRotator YawOnly = FRotator(0.f, Actor->GetActorRotation().Yaw, 0.f);
const FTransform ActorToWorld = FTransform(YawOnly.Quaternion());
const FTransform WorldToActor = ActorToWorld.Inverse();
Actor->ForEachComponent<UPrimitiveComponent>(true, [&](const UPrimitiveComponent* InPrimComp) {
// Ignore editor & blob shadow components
if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && !InPrimComp->bUseAttachParentBound && !InPrimComp->IsA<UMRUKBlobShadowComponent>())
{
const FTransform ComponentToActor = InPrimComp->GetComponentTransform() * WorldToActor;
Box += InPrimComp->CalcBounds(ComponentToActor).GetBox();
}
});
const FTransform Transform = Actor->GetTransform();
// Project 3D extent to 2D
const FVector ProjectedExtent = FVector::VectorPlaneProject(Box.GetExtent(), FVector::UpVector);
Origin = ActorToWorld.TransformPosition(Box.GetCenter());
Extent = FVector2D(ProjectedExtent);
Yaw = Transform.GetRotation().Rotator().Yaw;
}