Dynamic Decal on Landscape

Hello, I’m trying to get a dynamic decal that warps around terrain properly. It is a square indicator for where the camera is looking (later to be used as an indicator when selecting a location to build something).

Currently I am doing this by tracing each tick and drawing the decal to where they are looking. For flat terrain this work fine:

http://static.pantherdev.com/misc/terrain_flat.jpg

However, for hilly terrain it is clipped by the landscape:

http://static.pantherdev.com/misc/terrain_hill.jpg

I tried to destroy and recreate the decal when it needed to go to a new location, but it didn’t help. I’m hoping I am just misunderstanding something simple since I am so new to the platform.

Here is how the tick trace is currently implemented:

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

	FHitResult RV_Hit(EForceInit::ForceInit);
	if (bIsBuilding && DoTrace(&RV_Hit, &RV_InteractionTraceParams) && RV_Hit.bBlockingHit)
	{
		ShowBuildEffect(RV_Hit);
	}
	else
	{
		HideBuildEffect();
	}
}

FORCEINLINE void AMinervaCharacter::ShowBuildEffect(const FHitResult Impact)
{
	if (BuildSelectorEffect == NULL)
	{
		FActorSpawnParameters spawnParams = FActorSpawnParameters();
		spawnParams.Name = FName(TEXT("BuildSelector"));
		spawnParams.Owner = this;

		BuildSelectorEffect = GetWorld()->SpawnActor<ABuildSelectorEffect>(BuildSelectorTemplate, spawnParams);
	}

	BuildSelectorEffect->Show(Impact);
}

FORCEINLINE void AMinervaCharacter::HideBuildEffect()
{
	if (BuildSelectorEffect != NULL)
	{
		BuildSelectorEffect->Hide();
	}
}

bool AMinervaCharacter::DoTrace(FHitResult* RV_Hit, FCollisionQueryParams* RV_TraceParams)
{
	if (Controller == NULL) // access the controller, make sure we have one
	{
		return false;
	}

	// get the camera transform
	FVector CameraLoc;
	FRotator CameraRot;
	GetActorEyesViewPoint(CameraLoc, CameraRot);

	//setup the trace
	const FVector Start = CameraLoc;
	const FVector End = CameraLoc + (CameraRot.Vector() * PlayerInteractionDistance);

	// set some parameters
	RV_TraceParams->bTraceComplex = true;
	RV_TraceParams->bTraceAsyncScene = true;
	RV_TraceParams->bReturnPhysicalMaterial = true;

	// do the line trace
	bool DidTrace = GetWorld()->LineTraceSingle(
		*RV_Hit,   //result
		Start,     //start
		End,       //end
		ECollisionChannel::ECC_WorldStatic,  //collision channel
		*RV_TraceParams
	);

	return DidTrace;
}

Here is the BuildSelectorEffect’s Show/Hide methods:

void ABuildSelectorEffect::Hide()
{
	if (Decal != NULL)
	{
		Decal->DetachFromParent();
	}
}

void ABuildSelectorEffect::Show(FHitResult Impact)
{
	// no need to do anything if impact point is the same
	if (SurfaceHit.ImpactPoint == Impact.ImpactPoint) {
		return;
	}

	// update the surface hit we have stored
	SurfaceHit = Impact;

	if (Decal == NULL)
	{
		if (DefaultDecal.DecalMaterial)
		{
			// spawn a new decal at the new location
			Decal = UGameplayStatics::SpawnDecalAttached(
				DefaultDecal.DecalMaterial,
				FVector(DefaultDecal.DecalSize, DefaultDecal.DecalSize, 1.0f),
				SurfaceHit.Component.Get(),
				SurfaceHit.BoneName,
				SurfaceHit.ImpactPoint,
				SurfaceHit.ImpactNormal.Rotation(),
				EAttachLocation::KeepWorldPosition,
				DefaultDecal.LifeSpan
				);
		}
	}
	else
	{
		SurfaceHit.ImpactPoint.X = FMath::GridSnap(SurfaceHit.ImpactPoint.X, GridSnapSize);
		SurfaceHit.ImpactPoint.Y = FMath::GridSnap(SurfaceHit.ImpactPoint.Y, GridSnapSize);

		if (!Decal->IsAttachedTo(SurfaceHit.Component.Get())) {
			Decal->DetachFromParent();
			Decal->AttachTo(SurfaceHit.Component.Get());
		}

		Decal->SetWorldLocationAndRotation(SurfaceHit.ImpactPoint, SurfaceHit.ImpactNormal.Rotation());
	}
}

Hi ,

Have you checked the vertical bounds of the decal “box”? If the top of the box is at the same height as the flat terrain, then you will see this effect. You will also see this if the X scale of the decal box is set to 1.

Very cool, by increasing the X scale to an arbitrarily large number I was able to get the desired effect. This does seem pretty fragile though, is there a better way to guarantee the effect I want besides just making my X value huge?

Hi ,

I am assuming that you have the decal object centered on the hit location, correct? It should not be necessary to set the X scale to encompass all possible elevation values of your terrain. All you would need to do is make sure it is large enough to encompass the possible elevation differences (positive and negative) within the horizontal boundaries of the decal centered on the hit location at any point on your terrain. You can check this by finding the location in your terrain that has the steepest slope, and make sure the decal displays properly on that slope. Depending on your terrain, this should be a much smaller value.

In theory, it should also be possible to set the X scale value during runtime. I have not actually tried to do that before, so I am checking to see if it can be done. I will let you know what I find out.

I am using Decal->SetRelativeScale3D(FVector(DecalScaleX, DefaultDecal.DecalSize, DefaultDecal.DecalSize)); which seems to work fine. Thanks for your answer!