single line reflecting on all objects

Hi,

Here’s a DrawSingleReflect class whose CPP needs to be corrected because there is only one reflection regardless of the maximum number of reflections.
Here’s the .h:

// DrawSingleReflect.h
// But: Dessine un seul rayon qui se réfléchit sur les objets rencontrés.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DrawSingleReflect.generated.h"

UCLASS()
class ESSAI_API ADrawSingleReflect : public AActor
{
	GENERATED_BODY()

public:
	// Constructeur
	ADrawSingleReflect();

protected:
	// BeginPlay (appelé au démarrage)
	virtual void BeginPlay() override;

public:
	// Tick (appelé à chaque frame)
	virtual void Tick(float DeltaTime) override;

	// Acteur/objet qui est le point de départ du rayon initial
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	AActor* StartActor;

	// Longueur du rayon à chaque segment
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float RayLength = 1000.0f;

	// Épaisseur du rayon
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float LineThickness = 5.0f;

	// Durée du rayon (en secondes)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float LineDuration = -1.0f; // -1 pour une durée infinie

	// Couleur du rayon initial
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor LineColor = FLinearColor::Red;

	// Couleur du rayon réfléchi
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor ReflectedLineColor = FLinearColor::Green;

	// Couleur de la normale
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor NormalLineColor = FLinearColor::Blue;

	// Longueur de la normale
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float NormalLineLength = 100.0f;

	// Nombre maximum de réflexions
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	int32 MaxReflections = 5;

private:
	FVector CurrentRayStart;
	FVector CurrentRayDirection;
	int32 ReflectionCount;
};

and here the .cpp:

// DrawSingleReflect.cpp

#include "DrawSingleReflect.h"
#include "DrawDebugHelpers.h"
#include "Logging/LogMacros.h"
#include "Engine/World.h"

ADrawSingleReflect::ADrawSingleReflect()
{
	PrimaryActorTick.bCanEverTick = true;
	ReflectionCount = 0;
}

void ADrawSingleReflect::BeginPlay()
{
	Super::BeginPlay();

	if (StartActor)
	{
		CurrentRayStart = StartActor->GetActorLocation();
		CurrentRayDirection = StartActor->GetActorForwardVector();
		ReflectionCount = 0;
		UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::BeginPlay - Start: %s, Direction: %s"), *CurrentRayStart.ToString(), *CurrentRayDirection.ToString());
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::BeginPlay - StartActor non valide!"));
	}
}

void ADrawSingleReflect::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick() appelee, StartActor: %s, ReflectionCount: %d"), StartActor ? *StartActor->GetName() : TEXT("nullptr"), ReflectionCount);

	if (StartActor)
	{
		if (ReflectionCount < MaxReflections)
		{
			UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Current Direction: %s"), *CurrentRayDirection.ToString());

			FVector TraceEnd = CurrentRayStart + CurrentRayDirection * RayLength;
			FHitResult HitResult;
			FCollisionQueryParams CollisionParams;
			CollisionParams.AddIgnoredActor(this);
			CollisionParams.AddIgnoredActor(StartActor);

			UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Trace Start: %s, End: %s"), *CurrentRayStart.ToString(), *TraceEnd.ToString());

			bool bHit = GetWorld()->LineTraceSingleByChannel(
				HitResult,
				CurrentRayStart,
				TraceEnd,
				ECollisionChannel::ECC_Visibility,
				CollisionParams
			);

			// Dessin du segment de rayon actuel
			DrawDebugLine(
				GetWorld(),
				CurrentRayStart,
				TraceEnd,
				(ReflectionCount == 0) ? LineColor.ToFColor(true) : ReflectedLineColor.ToFColor(true),
				false,
				LineDuration,
				0,
				LineThickness
			);
			UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Ligne dessinee de %s a %s"), *CurrentRayStart.ToString(), *TraceEnd.ToString());

			if (bHit)
			{
				UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Hit detecte sur: %s a %s, Normal: %s"), HitResult.GetActor() ? *HitResult.GetActor()->GetName() : TEXT("nullptr"), *HitResult.ImpactPoint.ToString(), *HitResult.Normal.ToString());
				AActor* HitActor = HitResult.GetActor();
				if (HitActor)
				{
					FVector ImpactPoint = HitResult.ImpactPoint;
					FVector NormalVector = HitResult.Normal;
					FVector IncidentVector = (ImpactPoint - CurrentRayStart).GetSafeNormal();
					CurrentRayDirection = FMath::GetReflectionVector(IncidentVector, NormalVector);

					// *** INSERER L'EXTRAIT DE CODE ICI ***
					if (CurrentRayDirection.IsZero())
					{
						UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Vecteur reflechi nul, arret des reflexions."));
						// Option 1: Arrêter (comportement actuel)
						return;

						// Option 2: Choisir une direction par defaut (exemple: leger decalage vers le haut)
						// CurrentRayDirection = NormalVector.GetSafeNormal() + FVector(0.1f, 0.1f, 0.1f).GetSafeNormal();
					}
					// *** FIN DE L'INSERTION ***

					CurrentRayStart = ImpactPoint;
					ReflectionCount++;
					UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Reflexion %d"), ReflectionCount);

					// Dessin de la normale
					DrawDebugLine(
						GetWorld(),
						ImpactPoint,
						ImpactPoint + NormalVector * NormalLineLength,
						NormalLineColor.ToFColor(true),
						false,
						LineDuration,
						0,
						LineThickness
					);
					UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Normale dessinee a %s"), *ImpactPoint.ToString());
				}
				else
				{
					UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Hit sur un acteur non valide."));
					return; // Arrêter le traitement pour cette frame
				}
			
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::Tick - Aucun hit detecte."));
				return; // Arrêter le traitement pour cette frame
			}
		}
	}
}

Can you correct it?

thanks

A few things to try:
What happens if you set LineDuration to a positive value, like 20? I wonder if keeping it at -1 makes the debug line draw for a single frame. Also, if you want the line to be persistent, you can change that “false” value you are sending in both DrawDebugLine calls to true. That should set the ‘bPersistentLines’ flag which should make the lines stay on the screen.

Also, from looking at the code, it seems like one single line is calculated on each Tick. Something else you can try is changing the if (ReflectionCount < MaxReflections) to while (ReflectionCount < MaxReflections).

I’m not saying that any of these two changes will fix the issue, but it will help us debug what’s going on.

Here is the corrected code !
DrawSingleReflect.h:

// DrawSingleReflect.h
// But: Dessine un seul rayon qui se réfléchit sur les objets rencontrés.
//code OK

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DrawSingleReflect.generated.h"

UCLASS()
class ESSAI_API ADrawSingleReflect : public AActor
{
	GENERATED_BODY()

public:
	// Constructeur
	ADrawSingleReflect();

protected:
	// BeginPlay (appelé au démarrage)
	virtual void BeginPlay() override;

public:
	// Tick (appelé à chaque frame)
	virtual void Tick(float DeltaTime) override;

	// Acteur/objet qui est le point de départ du rayon initial
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	AActor* StartActor;

	// Longueur du rayon à chaque segment
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float RayLength = 1000.0f;

	// Épaisseur du rayon
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float LineThickness = 5.0f;

	// Durée du rayon (en secondes)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float LineDuration = -1.0f; // -1 pour une durée infinie

	// Couleur du rayon initial
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor LineColor = FLinearColor::Red;

	// Couleur du rayon réfléchi
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor ReflectedLineColor = FLinearColor::Green;

	// Couleur de la normale
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	FLinearColor NormalLineColor = FLinearColor::Blue;

	// Longueur de la normale
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	float NormalLineLength = 100.0f;

	// Nombre maximum de réflexions
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SingleReflect")
	int32 MaxReflections = 5;

private:
	FVector CurrentRayStart;
	FVector CurrentRayDirection;
	int32 ReflectionCount;

	void DrawReflectedRay(); // Ajouté

};

et DrawSingleReflect.cpp:

#include "DrawSingleReflect.h"
#include "DrawDebugHelpers.h"
#include "Logging/LogMacros.h"
#include "Engine/World.h"

ADrawSingleReflect::ADrawSingleReflect()
{
	PrimaryActorTick.bCanEverTick = true;
}

void ADrawSingleReflect::BeginPlay()
{
	Super::BeginPlay();

	if (!StartActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("ADrawSingleReflect::BeginPlay - StartActor non valide!"));
	}
}

void ADrawSingleReflect::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (!StartActor) return;

	// Réinitialise les points de départ à chaque tick
	CurrentRayStart = StartActor->GetActorLocation();
	CurrentRayDirection = StartActor->GetActorForwardVector();

	// Dessine les réflexions
	DrawReflectedRay();
}



void ADrawSingleReflect::DrawReflectedRay()
{
	int32 ReflectionCounter = 0;
	FVector RayStart = CurrentRayStart;
	FVector RayDirection = CurrentRayDirection.GetSafeNormal();

	FVector PreviousImpactPoint = FVector::ZeroVector;

	while (ReflectionCounter < MaxReflections)
	{
		FVector TraceEnd = RayStart + RayDirection * RayLength;

		FHitResult HitResult;
		FCollisionQueryParams Params;
		Params.AddIgnoredActor(this);
		Params.AddIgnoredActor(StartActor);

		bool bHit = GetWorld()->LineTraceSingleByChannel(
			HitResult,
			RayStart,
			TraceEnd,
			ECollisionChannel::ECC_Visibility,
			Params
		);

		// >>> DÉPLACER LE CODE DE DESSIN ICI <<<
		FVector DrawEnd = bHit ? HitResult.ImpactPoint : TraceEnd;

		DrawDebugLine(
			GetWorld(),
			RayStart,
			DrawEnd,
			(ReflectionCounter == 0) ? LineColor.ToFColor(true) : ReflectedLineColor.ToFColor(true),
			false,
			LineDuration,
			0,
			LineThickness
		);
		// >>> FIN DU DÉPLACEMENT <<<

		if (bHit && HitResult.GetActor())
		{
			FVector ImpactPoint = HitResult.ImpactPoint;
			FVector Normal = HitResult.Normal.GetSafeNormal();
			Params.AddIgnoredActor(HitResult.GetActor());

			// Stop si on est trop proche de l'impact précédent (évite les doubles rebonds au même point)
			if (FVector::DistSquared(PreviousImpactPoint, ImpactPoint) < 1.0f)
			{
				UE_LOG(LogTemp, Warning, TEXT("Impact trop proche de l'ancien. Arrêt."));
				break;
			}
			PreviousImpactPoint = ImpactPoint;

			// Dessin de la normale
			DrawDebugLine(
				GetWorld(),
				ImpactPoint,
				ImpactPoint + Normal * NormalLineLength,
				NormalLineColor.ToFColor(true),
				true,
				LineDuration,
				0,
				LineThickness
			);

			// Calcul du vecteur réfléchi
			FVector Reflected = FMath::GetReflectionVector(RayDirection, Normal).GetSafeNormal();

			if (Reflected.IsNearlyZero())
			{
				UE_LOG(LogTemp, Warning, TEXT("Vecteur reflechi nul. Arret."));
				break;
			}

			// Préparer le prochain rebond
			RayStart = ImpactPoint + Reflected * 0.5f; // Petit offset pour éviter rebond au même point
			RayDirection = Reflected;

			UE_LOG(LogTemp, Warning, TEXT("Réflexion %d: Impact sur %s à %s. Nouvelle direction: %s"),
				ReflectionCounter + 1,
				*HitResult.GetActor()->GetName(),
				*ImpactPoint.ToString(),
				*RayDirection.ToString()
			);

			UE_LOG(LogTemp, Warning, TEXT("RayStart: %s"), *RayStart.ToString());
			UE_LOG(LogTemp, Warning, TEXT("Hit point: %s"), *HitResult.ImpactPoint.ToString());
			UE_LOG(LogTemp, Warning, TEXT("Normal: %s"), *HitResult.Normal.ToString());
			UE_LOG(LogTemp, Warning, TEXT("Incoming Dir: %s"), *RayDirection.ToString());
			UE_LOG(LogTemp, Warning, TEXT("Reflected Dir: %s"), *Reflected.ToString());

			ReflectionCounter++;
		}
		else
		{
			break; // Plus rien à toucher
		}
	}

	UE_LOG(LogTemp, Warning, TEXT("Total réflexions: %d"), ReflectionCounter);
}