From c74a19c1dfe319ffd3bbfe4764741359ad199d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timon=20R=C3=B6mer?= <t.roemer@vr.rwth-aachen.de> Date: Fri, 22 Mar 2024 15:14:04 +0100 Subject: [PATCH] Comments and explains IntenSelect Component source code --- .../Private/Pawn/IntenSelectComponent.cpp | 322 +++++++++++++----- .../Public/Pawn/IntenSelectComponent.h | 152 ++++++++- 2 files changed, 379 insertions(+), 95 deletions(-) diff --git a/Source/RWTHVRToolkit/Private/Pawn/IntenSelectComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/IntenSelectComponent.cpp index d3970f5c..130b0efe 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/IntenSelectComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/IntenSelectComponent.cpp @@ -77,37 +77,57 @@ void UIntenSelectComponent::BeginPlay() InitInputBindings(); InitMaterialParamCollection(); + // Calculate sphere cast radius SphereCastRadius = CalculateSphereCastRadius(); + + // Set interaction distance to maximum selection distance InteractionDistance = MaxSelectionDistance; + // Set the component active based on the SetActiveOnStart flag SetActive(SetActiveOnStart, false); } void UIntenSelectComponent::InitInputBindings() { - const APawn* Pawn = Cast<APawn>(GetOwner()); - if (!Pawn) - return; - UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent); + // Get the player controller + const APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0); + + // Get the local player subsystem for enhanced input + UEnhancedInputLocalPlayerSubsystem* Subsystem = + ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()); - if (!EI) + // Get the player input component + UInputComponent* PlayerInputComponent = PC->InputComponent; + UEnhancedInputComponent* PEI = Cast<UEnhancedInputComponent>(PlayerInputComponent); + + // Check if the enhanced input component is valid + if (!PEI) { + // Display an error message and quit the game if the enhanced input component is not found const FString Message = "Could not get PlayerInputComponent for IntenSelect Input Assignment!"; +#if WITH_EDITOR + const FText Title = FText::FromString(FString("ERROR")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message), Title); +#endif UE_LOG(LogTemp, Error, TEXT("%s"), *Message) + UKismetSystemLibrary::QuitGame(this, nullptr, EQuitPreference::Quit, false); return; } - // Bind the actions - EI->BindAction(InputClick, ETriggerEvent::Started, this, &UIntenSelectComponent::OnFireDown); - EI->BindAction(InputClick, ETriggerEvent::Completed, this, &UIntenSelectComponent::OnFireUp); + // Bind the actions for input events + PEI->BindAction(InputClick, ETriggerEvent::Started, this, &UIntenSelectComponent::OnFireDown); + PEI->BindAction(InputClick, ETriggerEvent::Completed, this, &UIntenSelectComponent::OnFireUp); } void UIntenSelectComponent::InitSplineComponent() { + // Create a new spline component SplineComponent = NewObject<USplineComponent>(this, TEXT("SplineComponent")); + // Check if the spline component was successfully created if (SplineComponent) { + // Setup attachment and mobility of the spline component SplineComponent->SetupAttachment(this); SplineComponent->SetMobility(EComponentMobility::Movable); SplineComponent->RegisterComponent(); @@ -115,27 +135,32 @@ void UIntenSelectComponent::InitSplineComponent() } else { + // Display an error message if the spline component creation fails const FString Message = "Error while spawning SplineComponent!"; #if WITH_EDITOR const FText Title = FText::FromString(FString("ERROR")); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message), Title); #endif - UE_LOG(LogTemp, Error, TEXT("%s"), *Message) } } void UIntenSelectComponent::InitSplineMeshComponent() { + // Create a new spline mesh component SplineMeshComponent = NewObject<USplineMeshComponent>(this, USplineMeshComponent::StaticClass(), TEXT("SplineMeshComponent")); + + // Check if the spline mesh component was successfully created if (SplineMeshComponent) { + // Setup attachment and mobility of the spline mesh component SplineMeshComponent->SetupAttachment(this); SplineMeshComponent->SetMobility(EComponentMobility::Movable); SplineMeshComponent->RegisterComponent(); SplineMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + // Set the static mesh if available if (SplineMesh) { SplineMeshComponent->SetStaticMesh(SplineMesh); @@ -145,6 +170,7 @@ void UIntenSelectComponent::InitSplineMeshComponent() UE_LOG(LogTemp, Warning, TEXT("SplineMesh not set!")); } + // Set the material if available if (SplineMaterial) { SplineMeshComponent->SetMaterial(0, SplineMaterial); @@ -154,35 +180,43 @@ void UIntenSelectComponent::InitSplineMeshComponent() UE_LOG(LogTemp, Warning, TEXT("SplineMesh material not set! Using default material instead.")); } + // Set the forward axis and shadow casting properties SplineMeshComponent->SetForwardAxis(ESplineMeshAxis::Z); SplineMeshComponent->CastShadow = false; } else { + // Display an error message if the spline mesh component creation fails UE_LOG(LogTemp, Error, TEXT("Error while spawning SplineMeshComponent!")) } } + void UIntenSelectComponent::InitForwardRayMeshComponent() { + // Create a new static mesh component for the forward ray ForwardRayMeshComponent = NewObject<UStaticMeshComponent>(this, UStaticMeshComponent::StaticClass(), TEXT("ForwardRay")); + // Check if the forward ray mesh component was successfully created if (ForwardRayMeshComponent) { + // Setup attachment and mobility of the forward ray mesh component ForwardRayMeshComponent->SetupAttachment(this); - ForwardRayMeshComponent->SetMobility((EComponentMobility::Movable)); + ForwardRayMeshComponent->SetMobility(EComponentMobility::Movable); ForwardRayMeshComponent->RegisterComponent(); ForwardRayMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + // Configure shadow casting and collision properties ForwardRayMeshComponent->SetCastShadow(false); ForwardRayMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + // Set relative scale and location based on max selection distance const float MeshLength = MaxSelectionDistance > 1000 ? 1000 : MaxSelectionDistance; ForwardRayMeshComponent->SetRelativeScale3D(FVector(MeshLength, ForwardRayWidth, ForwardRayWidth)); ForwardRayMeshComponent->SetRelativeLocation(FVector(MeshLength * 50, 0, 0)); - // const ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Engine/BasicShapes/Cube.Cube")); + // Set the static mesh for the forward ray component if available if (ForwardRayMesh) { ForwardRayMeshComponent->SetStaticMesh(ForwardRayMesh); @@ -192,62 +226,76 @@ void UIntenSelectComponent::InitForwardRayMeshComponent() UE_LOG(LogTemp, Warning, TEXT("Mesh for RayComponent not set!")); } + // Create dynamic material instance for the forward ray mesh component UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(ForwardRayMaterial, ForwardRayMeshComponent); this->ForwardRayMeshComponent->SetMaterial(0, DynamicMaterial); + // Set visibility based on draw forward ray flag ForwardRayMeshComponent->SetHiddenInGame(!bDrawForwardRay); } else { + // Display an error message if the forward ray mesh component creation fails UE_LOG(LogTemp, Error, TEXT("Error while spawning ForwardRayMesh component!")); } } + void UIntenSelectComponent::InitMaterialParamCollection() { + // Check if the material parameter collection is set if (MaterialParamCollection) { + // Get the parameter collection instance from the world this->ParameterCollectionInstance = GetWorld()->GetParameterCollectionInstance(MaterialParamCollection); if (this->ParameterCollectionInstance) { + // Set the scalar parameter value for transparency this->ParameterCollectionInstance->SetScalarParameterValue("Transparency", DebugRayTransparency); } else { + // Display a warning if the parameter collection instance is not found UE_LOG(LogTemp, Warning, TEXT("MaterialParameterCollection required for rendering of IntenSelect could not be found!")) } } else { + // Display a warning if the material parameter collection is not set UE_LOG(LogTemp, Warning, TEXT("MaterialParameterCollection required for InteSelect visualization is not set!")); } } void UIntenSelectComponent::InitDebugConeMeshComponent() { + // Create a new static mesh component for the debug cone DebugConeMeshComponent = NewObject<UStaticMeshComponent>(this, UStaticMeshComponent::StaticClass(), TEXT("DebugCone")); + // Check if the debug cone mesh component was successfully created if (DebugConeMeshComponent) { + // Setup attachment and mobility of the debug cone mesh component DebugConeMeshComponent->SetupAttachment(this); DebugConeMeshComponent->SetMobility(EComponentMobility::Movable); DebugConeMeshComponent->RegisterComponent(); DebugConeMeshComponent->CreationMethod = EComponentCreationMethod::Instance; - + // Calculate transform for the cone based on selection cone angle and distance FTransform ConeTransform = DebugConeMeshComponent->GetRelativeTransform(); const float ConeScale = MaxSelectionDistance / 50 * FMath::Tan(FMath::DegreesToRadians(SelectionConeAngle)); ConeTransform.SetScale3D(FVector(ConeScale, ConeScale, MaxSelectionDistance / 100)); + // Set relative transform and location for the debug cone DebugConeMeshComponent->SetRelativeTransform(ConeTransform); DebugConeMeshComponent->SetRelativeLocation(FVector(MaxSelectionDistance - ConeBackwardShiftDistance, 0, 0), false); DebugConeMeshComponent->SetRelativeRotation(DebugConeRotation, false); DebugConeMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + // Set the static mesh for the debug cone component if available if (DebugConeMesh) { DebugConeMeshComponent->SetStaticMesh(DebugConeMesh); @@ -256,6 +304,8 @@ void UIntenSelectComponent::InitDebugConeMeshComponent() { UE_LOG(LogTemp, Warning, TEXT("DebugCone mesh not set!")) } + + // Set the material for the debug cone component if available if (DebugConeMaterial) { DebugConeMeshComponent->SetMaterial(0, DebugConeMaterial); @@ -265,10 +315,12 @@ void UIntenSelectComponent::InitDebugConeMeshComponent() UE_LOG(LogTemp, Warning, TEXT("DebugCone material not set! Using default material instead.")) } + // Set visibility based on draw debug cone flag DebugConeMeshComponent->SetVisibility(bDrawDebugCone); } else { + // Display an error message if the debug cone mesh component creation fails UE_LOG(LogTemp, Error, TEXT("Error while spawning DebugCone component!")) } } @@ -278,59 +330,72 @@ void UIntenSelectComponent::InitDebugConeMeshComponent() float UIntenSelectComponent::CalculateSphereCastRadius() const { + // Calculate sphere cast radius based on selection cone angle and max selection distance return FMath::Tan(FMath::DegreesToRadians(SelectionConeAngle)) * MaxSelectionDistance; } + bool UIntenSelectComponent::CheckPointInCone(const FVector ConeStartPoint, const FVector ConeForward, const FVector PointToTest, const float Angle) const { + // Shift the start origin point of the cone backward const FVector ShiftedStartOriginPoint = ConeStartPoint - (ConeForward * ConeBackwardShiftDistance); + + // Calculate the direction to the test point const FVector DirectionToTestPoint = (PointToTest - ShiftedStartOriginPoint).GetSafeNormal(); + // Calculate the angle to the test point const float AngleToTestPoint = FMath::RadiansToDegrees(FMath::Acos((FVector::DotProduct(ConeForward, DirectionToTestPoint)))); + // Check if the angle to the test point is within the specified cone angle return AngleToTestPoint <= Angle; } + void UIntenSelectComponent::OnNewSelected_Implementation(UIntenSelectable* Selection) { + // Set the current selection CurrentSelection = Selection; + // Play sound feedback if cooldown allows if (FeedbackCooldown == 0) { - // UGameplayStatics::GetPlayerController(GetWorld(), 0)->PlayHapticEffect(SelectionFeedbackHaptic, - // EControllerHand::Right, 0.1, false); UGameplayStatics::PlaySound2D(GetWorld(), OnSelectSound); FeedbackCooldown = 0.1; } } + bool UIntenSelectComponent::GetActorsFromSphereCast(const FVector& SphereCastStart, TArray<FHitResult>& OutHits) const { + // Calculate start and end positions for the sphere cast const FVector StartPos = SphereCastStart + (GetComponentTransform().GetRotation().GetForwardVector() * SphereCastRadius); - const FVector EndPos = - StartPos + (this->GetComponentTransform().GetRotation().GetForwardVector() * (MaxSelectionDistance)); + const FVector EndPos = StartPos + (GetComponentTransform().GetRotation().GetForwardVector() * MaxSelectionDistance); + // Set up collision query parameters const FCollisionQueryParams Params = FCollisionQueryParams(FName(TEXT("SphereTraceMultiForObjects")), false); - // GetWorld()->SweepMultiByChannel(OutHits, StartPos, EndPos, FQuat::Identity, ECC_Visibility, - // FCollisionShape::MakeSphere(SphereCastRadius), Params); + // Perform sphere cast GetWorld()->SweepMultiByChannel(OutHits, StartPos, EndPos, FQuat::Identity, ECC_Visibility, FCollisionShape::MakeSphere(SphereCastRadius), Params); - // UKismetSystemLibrary::SphereTraceMulti(GetWorld(),StartPos,EndPos,SphereCastRadius,ETraceTypeQuery::TraceTypeQuery1,false,{},EDrawDebugTrace::ForOneFrame,OutHits,true); + return true; } + UIntenSelectable* UIntenSelectComponent::GetMaxScoreActor(const float DeltaTime) { - const FVector ConeOrigin = this->GetComponentTransform().GetLocation(); - const FVector ConeForward = this->GetComponentTransform().GetRotation().GetForwardVector(); + // Get cone origin and forward direction + const FVector ConeOrigin = GetComponentTransform().GetLocation(); + const FVector ConeForward = GetComponentTransform().GetRotation().GetForwardVector(); + // Perform sphere cast to detect selectable actors TArray<FHitResult> OutHits; if (GetActorsFromSphereCast(ConeOrigin, OutHits)) { + // Iterate through hit results for (const FHitResult& Hit : OutHits) { const FVector PointToCheck = Hit.ImpactPoint; @@ -339,36 +404,39 @@ UIntenSelectable* UIntenSelectComponent::GetMaxScoreActor(const float DeltaTime) const AActor* HitActor = Hit.GetActor(); if (HitActor) { - const auto Selectable = HitActor->FindComponentByClass<UIntenSelectable>(); - + // Check if the hit actor is selectable and within selection distance + UIntenSelectable* Selectable = HitActor->FindComponentByClass<UIntenSelectable>(); if (Selectable && Selectable->IsSelectable && DistanceToActor <= MaxSelectionDistance) { + // Add to score map ScoreMap.FindOrAdd(Selectable, 0); } } } } + // Variables for tracking the maximum score selectable UIntenSelectable* MaxScoreSelectable = nullptr; float MaxScore = TNumericLimits<float>::Min(); TArray<UIntenSelectable*> RemoveList; TArray<TPair<UIntenSelectable*, FHitResult>> CandidateList; - for (TTuple<UIntenSelectable*, float>& OldScoreEntry : ScoreMap) + // Iterate through the score map + for (TPair<UIntenSelectable*, float>& OldScoreEntry : ScoreMap) { - // GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Black, OldScoreEntry.Key->GetOwner()->GetName() + " - // - Score: " + FString::SanitizeFloat(OldScoreEntry.Value)); if (!OldScoreEntry.Key) { continue; } + // Calculate the new score and contact point TPair<FHitResult, float> NewScorePair = OldScoreEntry.Key->GetBestPointScorePair( ConeOrigin, ConeForward, ConeBackwardShiftDistance, SelectionConeAngle, OldScoreEntry.Value, DeltaTime); ContactPointMap.Add(OldScoreEntry.Key, NewScorePair.Key); const float DistanceToActor = FVector::Dist(ConeOrigin, NewScorePair.Key.ImpactPoint); + // Check if the new score is valid and if the actor is still selectable const float Eps = 0.01; if (NewScorePair.Value <= 0.01 || DistanceToActor >= MaxSelectionDistance || !OldScoreEntry.Key->IsSelectable) { @@ -378,15 +446,16 @@ UIntenSelectable* UIntenSelectComponent::GetMaxScoreActor(const float DeltaTime) { OldScoreEntry.Value = NewScorePair.Value; + // Check if the new score exceeds the maximum score if (NewScorePair.Value > (1.0 - Eps) && - this->CheckPointInCone(ConeOrigin, ConeForward, NewScorePair.Key.ImpactPoint, SelectionConeAngle)) + CheckPointInCone(ConeOrigin, ConeForward, NewScorePair.Key.ImpactPoint, SelectionConeAngle)) { CandidateList.Emplace(OldScoreEntry.Key, NewScorePair.Key); MaxScore = NewScorePair.Value; MaxScoreSelectable = OldScoreEntry.Key; } else if (NewScorePair.Value > MaxScore && - this->CheckPointInCone(ConeOrigin, ConeForward, NewScorePair.Key.ImpactPoint, SelectionConeAngle)) + CheckPointInCone(ConeOrigin, ConeForward, NewScorePair.Key.ImpactPoint, SelectionConeAngle)) { MaxScore = NewScorePair.Value; MaxScoreSelectable = OldScoreEntry.Key; @@ -394,19 +463,22 @@ UIntenSelectable* UIntenSelectComponent::GetMaxScoreActor(const float DeltaTime) } } - for (const UIntenSelectable* i : RemoveList) + // Remove non-selectable actors from the maps + for (UIntenSelectable* i : RemoveList) { ContactPointMap.Remove(i); ScoreMap.Remove(i); } + + // Select the nearest actor from the candidate list if available if (CandidateList.Num() > 0) { - auto DistanceToMaxScore = + float DistanceToMaxScore = FVector::Distance(MaxScoreSelectable->GetOwner()->GetActorLocation(), GetComponentLocation()); - auto Dist = TNumericLimits<float>::Max(); + float Dist = TNumericLimits<float>::Max(); for (const TPair<UIntenSelectable*, FHitResult>& Actor : CandidateList) { - const auto DistanceToCandidate = FVector::Distance(Actor.Value.ImpactPoint, GetComponentLocation()); + const float DistanceToCandidate = FVector::Distance(Actor.Value.ImpactPoint, GetComponentLocation()); if (DistanceToCandidate < Dist) { MaxScoreSelectable = Actor.Key; @@ -417,78 +489,86 @@ UIntenSelectable* UIntenSelectComponent::GetMaxScoreActor(const float DeltaTime) return MaxScoreSelectable; } + // RAYCASTING void UIntenSelectComponent::HandleWidgetInteraction() { - const FVector Forward = this->GetComponentTransform().GetRotation().GetForwardVector(); - const FVector Origin = this->GetComponentTransform().GetLocation(); + // Get forward vector and origin of the component + const FVector Forward = GetComponentTransform().GetRotation().GetForwardVector(); + const FVector Origin = GetComponentTransform().GetLocation(); + // Raytrace to find the first hit TOptional<FHitResult> Hit = RaytraceForFirstHit(Origin, Origin + Forward * MaxSelectionDistance); + // If no hit, clear focus and return if (!Hit.IsSet()) { IsWidgetInFocus = false; return; } + // Set hit result and check if a widget is in focus SetCustomHitResult(Hit.GetValue()); UWidgetComponent* FocusedWidget = Cast<UWidgetComponent>(Hit.GetValue().GetComponent()); IsWidgetInFocus = (FocusedWidget != nullptr); - + // Handle widget events (commented out for now) /* - if(IsWidgetInFocus) + if (IsWidgetInFocus) { if (FocusedWidget != LastFocusedWidget) { - //We can always execute the enter event as we are sure that a hit occured if (FocusedWidget->GetOwner()->Implements<UTargetable>()) { ITargetable::Execute_OnTargetedEnter(FocusedWidget->GetOwner()); } - //Only execute the Leave Event if there was an actor that was focused previously if (LastFocusedWidget != nullptr && LastFocusedWidget->GetOwner()->Implements<UTargetable>()) { ITargetable::Execute_OnTargetedLeave(LastFocusedWidget->GetOwner()); } } - // for now uses the same distance as clicking if (FocusedWidget->GetOwner()->Implements<UTargetable>()) { ITargetable::Execute_OnTargeted(FocusedWidget->GetOwner(), Hit->Location); } LastFocusedWidget = FocusedWidget; - if(FocusedWidget->GetOwner()->GetClass()->ImplementsInterface(UIntenSelectableWidget::StaticClass())) + if (FocusedWidget->GetOwner()->GetClass()->ImplementsInterface(UIntenSelectableWidget::StaticClass())) { FVector pos = IIntenSelectableWidget::Execute_GetCoordinates(FocusedWidget->GetOwner()); GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Black, "C++ Pos: " + pos.ToString()); WidgetFocusPoint = pos; - }else + } + else { GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Black, "C++ Pos not available"); } - }*/ + } + */ } + TOptional<FHitResult> UIntenSelectComponent::RaytraceForFirstHit(const FVector& Start, const FVector& End) const { - // will be filled by the Line Trace Function + // Hit result to be filled by Line Trace function FHitResult Hit; + // Set up collision query parameters FCollisionQueryParams Params; - Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself + Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // Ignore the owner actor to prevent hitting itself + + // Perform line trace if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility, Params)) { return {Hit}; } else { - return {}; + return {}; // No hit } } @@ -497,78 +577,106 @@ TOptional<FHitResult> UIntenSelectComponent::RaytraceForFirstHit(const FVector& void UIntenSelectComponent::DrawSelectionCurve(const FVector& EndPoint) const { - const FVector StartPoint = this->GetComponentTransform().GetLocation(); - const FVector Forward = this->GetComponentTransform().GetRotation().GetForwardVector(); + // Get start point, forward vector, and set spline points + const FVector StartPoint = GetComponentTransform().GetLocation(); + const FVector Forward = GetComponentTransform().GetRotation().GetForwardVector(); SplineComponent->ClearSplinePoints(true); SplineMeshComponent->SetHiddenInGame(false); AddSplinePointsDefault(StartPoint, Forward, EndPoint); + // Get spline start and end positions and tangents const FVector StartPosition = SplineComponent->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::Local); const FVector StartTangent = SplineComponent->GetTangentAtSplinePoint(0, ESplineCoordinateSpace::Local); const FVector EndPosition = SplineComponent->GetLocationAtSplinePoint(1, ESplineCoordinateSpace::Local); const FVector EndTangent = SplineComponent->GetTangentAtSplinePoint(1, ESplineCoordinateSpace::Local); + // Set start and end for spline mesh component SplineMeshComponent->SetStartAndEnd(StartPosition, StartTangent, EndPosition, EndTangent, true); } + void UIntenSelectComponent::AddSplinePointsDefault(const FVector& StartPoint, const FVector& Forward, const FVector& EndPoint) const { + // Add start and end points to the spline SplineComponent->AddSplineWorldPoint(StartPoint); - - const FVector StartToEnd = EndPoint - StartPoint; - const FVector ForwardProjection = StartToEnd.ProjectOnTo(Forward); - SplineComponent->AddSplineWorldPoint(EndPoint); + // Set spline point types SplineComponent->SetSplinePointType(0, ESplinePointType::Curve, true); SplineComponent->SetSplinePointType(1, ESplinePointType::Curve, true); - SplineComponent->SetTangentAtSplinePoint(0, Forward * ForwardProjection.Size() * SplineCurvatureStrength, - ESplineCoordinateSpace::World, true); - SplineComponent->SetTangentAtSplinePoint(1, StartToEnd.GetSafeNormal(), ESplineCoordinateSpace::World, true); + // Calculate tangents for smooth curve + const FVector StartToEnd = EndPoint - StartPoint; + const FVector ForwardProjection = StartToEnd.ProjectOnTo(Forward); + const FVector StartTangent = Forward * ForwardProjection.Size() * SplineCurvatureStrength; + const FVector EndTangent = StartToEnd.GetSafeNormal(); + + // Set tangents at spline points + SplineComponent->SetTangentAtSplinePoint(0, StartTangent, ESplineCoordinateSpace::World, true); + SplineComponent->SetTangentAtSplinePoint(1, EndTangent, ESplineCoordinateSpace::World, true); } + void UIntenSelectComponent::UpdateForwardRay(const FVector& ReferencePoint) const { + // Check if transparency curve is available if (ForwardRayTransparencyCurve) { - const FVector ConeForward = this->GetComponentTransform().GetRotation().GetForwardVector(); - const FVector ConeOrigin = - this->GetComponentTransform().GetLocation() - (ConeForward * ConeBackwardShiftDistance); + // Calculate cone forward vector and origin + const FVector ConeForward = GetComponentTransform().GetRotation().GetForwardVector(); + const FVector ConeOrigin = GetComponentTransform().GetLocation() - (ConeForward * ConeBackwardShiftDistance); + // Calculate angle to test point const FVector TestPointVector = (ReferencePoint - ConeOrigin).GetSafeNormal(); const float AngleToTestPoint = - FMath::RadiansToDegrees(FMath::Acos((FVector::DotProduct(ConeForward, TestPointVector)))); + FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ConeForward, TestPointVector))); + // Calculate new transparency based on curve const float NewTransparency = ForwardRayTransparencyCurve->GetFloatValue(AngleToTestPoint / SelectionConeAngle) * DebugRayTransparency; - ParameterCollectionInstance->SetScalarParameterValue("Transparency", NewTransparency); + + // Set transparency parameter value in parameter collection + if (ParameterCollectionInstance) + { + ParameterCollectionInstance->SetScalarParameterValue("Transparency", NewTransparency); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("ParameterCollectionInstance is null!")); + } + } + else + { + UE_LOG(LogTemp, Warning, TEXT("ForwardRayTransparencyCurve is null!")); } } + // INPUT-HANDLING void UIntenSelectComponent::OnFireDown() { - // start interaction of WidgetInteractionComponent + // Start interaction of WidgetInteractionComponent PressPointerKey(EKeys::LeftMouseButton); + // Check if there is a current selection if (!CurrentSelection) { return; } - if (CurrentSelection) + // Handle action start events for current selection + const FHitResult* GrabbedPoint = ContactPointMap.Find(CurrentSelection); + if (GrabbedPoint) { - const FHitResult GrabbedPoint = *ContactPointMap.Find(CurrentSelection); CurrentSelection->HandleOnActionStartEvents(this); LastKnownSelection = CurrentSelection; LastKnownGrabPoint = LastKnownSelection->GetOwner()->GetRootComponent()->GetComponentTransform().InverseTransformPosition( - GrabbedPoint.ImpactPoint); + GrabbedPoint->ImpactPoint); } else { @@ -577,19 +685,22 @@ void UIntenSelectComponent::OnFireDown() IsGrabbing = true; + // Update transparency if required if (bDrawForwardRay && ParameterCollectionInstance) { ParameterCollectionInstance->SetScalarParameterValue("Transparency", 0); } } + void UIntenSelectComponent::OnFireUp() { - // end interaction of WidgetInteractionComponent + // End interaction of WidgetInteractionComponent ReleasePointerKey(EKeys::LeftMouseButton); IsGrabbing = false; + // Handle action end events for last known selection if (LastKnownSelection) { FInputActionValue v; @@ -597,136 +708,165 @@ void UIntenSelectComponent::OnFireUp() } } + // SELECTION-HANDLING void UIntenSelectComponent::SelectObject(UIntenSelectable* SelectableComponent, AActor* SelectedBy) { + // Set the current selection to the specified selectable component CurrentSelection = SelectableComponent; } + void UIntenSelectComponent::Unselect() { + // Stop grabbing IsGrabbing = false; + // Hide spline mesh component SplineMeshComponent->SetHiddenInGame(true); + // Reset current selection CurrentSelection = nullptr; - this->CurrentSelection = nullptr; } + void UIntenSelectComponent::SetActive(bool bNewActive, bool bReset) { if (bNewActive) { + // Show forward ray and spline mesh components ForwardRayMeshComponent->SetVisibility(true); SplineMeshComponent->SetVisibility(true); + // Call superclass setActive function Super::SetActive(true, bReset); } else { + // If there is a current selection, handle no actor selected if (CurrentSelection) { HandleNoActorSelected(); } + // If there is a last known selection, end the fire action if (LastKnownSelection) { OnFireUp(); } + // Hide forward ray and spline mesh components ForwardRayMeshComponent->SetVisibility(false); SplineMeshComponent->SetVisibility(false); + // Call superclass setActive function Super::SetActive(false, bReset); } } + // TICK void UIntenSelectComponent::HandleCooldown(const float DeltaTime) { + // Reduce feedback cooldown by delta time if (FeedbackCooldown > 0) { FeedbackCooldown -= DeltaTime; } + // Ensure feedback cooldown does not go below 0 else { FeedbackCooldown = 0; } } -void UIntenSelectComponent::HandleGrabbing(const float DeltaTime) const {} - void UIntenSelectComponent::HandleActorSelected(UIntenSelectable* NewSelection) { + // Check if the new selection is different from the current selection if (NewSelection != CurrentSelection) { + // If there is a current selection, handle hover end events if (CurrentSelection) { CurrentSelection->HandleOnHoverEndEvents(this); } + // If there is a new selection, handle hover start events if (NewSelection) { - UIntenSelectable* NewIntenSelectable = NewSelection; - const FHitResult GrabbedPoint = *ContactPointMap.Find(NewIntenSelectable); - NewIntenSelectable->HandleOnHoverStartEvents(this, GrabbedPoint); + const FHitResult* GrabbedPoint = ContactPointMap.Find(NewSelection); + if (GrabbedPoint) + { + NewSelection->HandleOnHoverStartEvents(this, *GrabbedPoint); + } } + // Set the new selection as the current selection and trigger new selected event CurrentSelection = NewSelection; OnNewSelected(NewSelection); } + // If there is a current selection, update forward ray and draw selection curve if (CurrentSelection) { - const UIntenSelectable* NewIntenSelectable = NewSelection; - const auto V_Net = ContactPointMap.Find(NewIntenSelectable)->ImpactPoint; - const FVector PointToDrawTo = ConvertNetVector(V_Net); - - if (bDrawForwardRay) + const FHitResult* GrabbedPoint = ContactPointMap.Find(NewSelection); + if (GrabbedPoint) { - UpdateForwardRay(PointToDrawTo); - } + const FVector PointToDrawTo = ConvertNetVector(GrabbedPoint->ImpactPoint); - DrawSelectionCurve(PointToDrawTo); + if (bDrawForwardRay) + { + UpdateForwardRay(PointToDrawTo); + } + + DrawSelectionCurve(PointToDrawTo); + } } } + FVector UIntenSelectComponent::ConvertNetVector(FVector_NetQuantize v) { - FVector Result; - Result.X = v.X; - Result.Y = v.Y; - Result.Z = v.Z; - return Result; + // Convert NetQuantize vector to FVector + return FVector(v.X, v.Y, v.Z); } void UIntenSelectComponent::HandleNoActorSelected() { + // Hide spline mesh component SplineMeshComponent->SetHiddenInGame(true); + // If there is a current selection, handle hover end events and reset current selection if (CurrentSelection) { CurrentSelection->HandleOnHoverEndEvents(this); + CurrentSelection = nullptr; } + // Reset transparency parameter if drawing forward ray if (bDrawForwardRay && ParameterCollectionInstance) { ParameterCollectionInstance->SetScalarParameterValue("Transparency", DebugRayTransparency); } - CurrentSelection = nullptr; } + void UIntenSelectComponent::TickComponent(const float DeltaTime, const ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { + // Call parent tick function Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - this->HandleCooldown(DeltaTime); + // Handle cooldown for feedback + HandleCooldown(DeltaTime); + + // Get the new selection based on current parameters UIntenSelectable* const NewSelection = GetMaxScoreActor(DeltaTime); + // If currently grabbing an object, update selection curve and check for angle constraints if (IsGrabbing && LastKnownSelection) { const FVector GrabPointWorld = @@ -734,10 +874,10 @@ void UIntenSelectComponent::TickComponent(const float DeltaTime, const ELevelTic LastKnownGrabPoint); DrawSelectionCurve(GrabPointWorld); - const FVector ConeOrigin = this->GetComponentLocation(); - const FVector ConeForward = this->GetForwardVector().GetSafeNormal(); + const FVector ConeOrigin = GetComponentLocation(); + const FVector ConeForward = GetForwardVector().GetSafeNormal(); - if (!this->CheckPointInCone(ConeOrigin, ConeForward, GrabPointWorld, MaxClickStickAngle)) + if (!CheckPointInCone(ConeOrigin, ConeForward, GrabPointWorld, MaxClickStickAngle)) { OnFireUp(); } @@ -750,11 +890,10 @@ void UIntenSelectComponent::TickComponent(const float DeltaTime, const ELevelTic DrawSelectionCurve(GrabbedPoint); } - // this->HandleWidgetInteraction(); + // Handle widget interaction IsWidgetInFocus = false; if (IsWidgetInFocus) { - // GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Red, "Widget focused"); HandleNoActorSelected(); const FVector PointToDrawTo = WidgetFocusPoint; @@ -768,14 +907,13 @@ void UIntenSelectComponent::TickComponent(const float DeltaTime, const ELevelTic } else { + // If there is a new selection, handle it; otherwise, handle no actor selected if (NewSelection) { - // GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Red, "Focused Actor:" + NewSelection->GetName()); HandleActorSelected(NewSelection); } else { - // GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0, FColor::Red, "No Actor in Focus"); HandleNoActorSelected(); } } diff --git a/Source/RWTHVRToolkit/Public/Pawn/IntenSelectComponent.h b/Source/RWTHVRToolkit/Public/Pawn/IntenSelectComponent.h index 864e5dee..5142f339 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/IntenSelectComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/IntenSelectComponent.h @@ -143,71 +143,217 @@ public: #pragma region /** INITIALIZATION */ private: + /** + * \brief Initializes the input bindings for interacting with the component. + */ void InitInputBindings(); + + /** + * \brief Initializes the debug cone mesh component used for visualization. + */ void InitDebugConeMeshComponent(); + + /** + * \brief Initializes the spline mesh component used for visualization. + */ void InitSplineMeshComponent(); + + /** + * \brief Initializes the spline component used for visualization. + */ void InitSplineComponent(); + + /** + * \brief Initializes the forward ray mesh component used for visualization. + */ void InitForwardRayMeshComponent(); + + /** + * \brief Initializes the material parameter collection used for visualization. + */ void InitMaterialParamCollection(); + #pragma endregion #pragma region /** SCORING */ + private: + /** + * \brief Calculates the radius for sphere casting based on the selection cone angle and maximum selection distance. + * \return The calculated sphere cast radius. + */ float CalculateSphereCastRadius() const; + + /** + * \brief Retrieves actors from sphere casting to determine potential selections. + * \param SphereCastStart The starting point of the sphere cast. + * \param OutHits The array to store hit results. + * \return True if successful, false otherwise. + */ bool GetActorsFromSphereCast(const FVector& SphereCastStart, TArray<FHitResult>& OutHits) const; + + /** + * \brief Checks if a point is within the selection cone. + * \param ConeStartPoint The starting point of the selection cone. + * \param ConeForward The forward direction of the selection cone. + * \param PointToTest The point to test for inclusion in the cone. + * \param Angle The angle of the selection cone. + * \return True if the point is within the cone, false otherwise. + */ bool CheckPointInCone(const FVector ConeStartPoint, const FVector ConeForward, const FVector PointToTest, const float Angle) const; + + /** + * \brief Determines the actor with the maximum score for selection. + * \param DeltaTime The time elapsed since the last frame. + * \return The actor with the maximum score for selection. + */ UIntenSelectable* GetMaxScoreActor(const float DeltaTime); + #pragma endregion + #pragma region /** VISUALS */ + private: + /** + * \brief Draws a selection curve from the component to the specified end point. + * \param EndPoint The end point of the selection curve. + */ void DrawSelectionCurve(const FVector& EndPoint) const; + + /** + * \brief Adds default spline points for creating a spline between start and end points. + * \param StartPoint The starting point of the spline. + * \param Forward The forward direction of the spline. + * \param EndPoint The end point of the spline. + */ void AddSplinePointsDefault(const FVector& StartPoint, const FVector& Forward, const FVector& EndPoint) const; + + /** + * \brief Updates the forward ray visualization based on the reference point. + * \param ReferencePoint The reference point to update the forward ray to. + */ void UpdateForwardRay(const FVector& ReferencePoint) const; + #pragma endregion + #pragma region /** RAYCASTING */ + private: + /** + * \brief Handles the interaction with widgets. + */ void HandleWidgetInteraction(); + + /** + * \brief Performs a raycast from the start to the end point and returns the first hit result, if any. + * \param Start The starting point of the raycast. + * \param End The ending point of the raycast. + * \return The optional hit result of the raycast. + */ TOptional<FHitResult> RaytraceForFirstHit(const FVector& Start, const FVector& End) const; + #pragma endregion + #pragma region /** INPUT-HANDLING */ + private: + /** + * \brief Handles the input event when the fire button is pressed. + */ UFUNCTION(BlueprintCallable) void OnFireDown(); + + /** + * \brief Handles the input event when the fire button is released. + */ UFUNCTION(BlueprintCallable) void OnFireUp(); + #pragma endregion + #pragma region /** OTHER */ + private: + /** + * \brief Handles the cooldown mechanism based on the passed time interval. + * \param DeltaTime The time elapsed since the last frame. + */ void HandleCooldown(const float DeltaTime); - void HandleGrabbing(const float DeltaTime) const; + + /** + * \brief Handles the scenario when no actor is selected. + */ void HandleNoActorSelected(); + + /** + * \brief Handles the selection of a new actor. + * \param NewSelection The new selectable component to be selected. + */ void HandleActorSelected(UIntenSelectable* NewSelection); + + /** + * \brief Converts a network quantized vector to a regular FVector. + * \param v The network quantized vector to convert. + * \return The converted FVector. + */ FVector ConvertNetVector(FVector_NetQuantize v); + #pragma endregion + public: + /** + * \brief Constructor for the UIntenSelectComponent class. + */ UIntenSelectComponent(const FObjectInitializer& ObjectInitializer); #pragma region /** SELECTION */ + + /** + * \brief Selects a given selectable component. + * \param SelectableComponent The selectable component to be selected. + * \param SelectedBy The actor responsible for the selection. + */ void SelectObject(UIntenSelectable* SelectableComponent, AActor* SelectedBy); + + /** + * \brief Unselects the currently selected object. + */ void Unselect(); + #pragma endregion + /** + * \brief Sets whether the component is active or not. + * \param bNewActive The new active state of the component. + * \param bReset Whether the activation should happen even if ShouldActivate returns false. + */ virtual void SetActive(bool bNewActive, bool bReset) override; + /** + * \brief Blueprint-native event called when a new object is selected. + * \param Selection The newly selected object. + */ UFUNCTION(BlueprintNativeEvent) void OnNewSelected(UIntenSelectable* Selection); protected: - // Called when the game starts + /** + * \brief Called when the game starts. + */ virtual void BeginPlay() override; - // Called every frame + /** + * \brief Called every frame. + * \param DeltaTime The time since the last frame. + * \param TickType The type of tick. + * \param ThisTickFunction The tick function for this component. + */ virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; }; -- GitLab