How to change Decal's rotation at runtime relative to the floor or wall? C++

I have done this before successfully and for some reason the decal will not spawn anymore. I have the correct collision channels ticked I also created my own specific collision channel for the linetrace that is going to create the exit hole. One day I don’t know what happened now either my code is now irrelevant or the engine has a bug. I don’t understand why this is happening.
Here is the code:

FCollisionResponseParams ResponseParams;
FCollisionQueryParams RifleQueryParams = FCollisionQueryParams::DefaultQueryParam;
FActorSpawnParameters SpawnParams;
RifleQueryParams.bReturnPhysicalMaterial = true;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
RifleQueryParams.AddIgnoredComponent(WeaponBaseMesh);
TArray<FHitResult>Hit;
const FVector RifleStartTrace = WeaponBaseMesh->GetSocketLocation(FName("BarrelSocket"));
FRotator CurrentRotation = WeaponBaseMesh->GetSocketRotation(FName("BarrelSocket"));
 FVector EndTrace =  RifleStartTrace + CurrentRotation.Vector() * 10000.0f;
DrawDebugLine(GetWorld(), RifleStartTrace, EndTrace, FColor::Red, false, 1.0f, 0, 1.0);
//Here is the multiline trace by channel causing the first hit.
if (GetWorld()->LineTraceMultiByChannel(Hit, RifleStartTrace, EndTrace, ECollisionChannel::ECC_GameTraceChannel1, RifleQueryParams, ResponseParams))
{
for (FHitResult& Results : EnemyHit)
{
//Here is the first result.
if (AEnemy* Enemy = Cast<AEnemy>(Results.GetActor()))
{
FHitResult LineTraceEndHit2;
FCollisionResponseParams ResponseLineParams;
FCollisionQueryParams LineQueryParams = FCollisionQueryParams(SCENE_QUERY_STAT(WeaponTrace), false, this);
FActorSpawnParameters LineSpawnParams;
LineSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
LineQueryParams.AddIgnoredComponent(WeaponBaseMesh);
const FVector ExitLineStartTrace = StartTrace;
FRotator ExitLineCurrentRotation = EndTrace.Rotation();
FVector  ExitLineEndTrace = EndTrace + ExitLineCurrentRotation .Vector();
DrawDebugLine(GetWorld(), ExitLineStartTrace , ExitLineEndTrace , FColor(255, 0, 0), false, -1, 0, 5.333);
//Here is the second line trace. I don't understand why the second is not firing off correctly I know I am attaching it to the exit point of the first line trace which hits the character. Now I know the second line trace much activate in order to create a exit hole. 
if (GetWorld()->LineTraceSingleByChannel(LineTraceEndHit2, ExitLineStartTrace , ExitLineEndTrace, ECollisionChannel::ECC_GameTraceChannel4, LineQueryParams , ResponseLineParams))
{
   //Here is where the second line trace fires once the enemy has been hit. 
if (AActor* Actor = Cast<AActor>(LineTraceEndHit2.GetActor()))
{
GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Orange, FString::Printf(TEXT("Blood Has spawned")));
FVector DecalSize(20.0f, 20.0f, 20.0f);
FRotator RandomDecalRotation = LineTraceEndHit2.TraceEnd.Rotation();
RandomDecalRotation = FRotator(90,0,0);
FVector DecalLocation = LineTraceEndHit2.Location;
UGameplayStatics::SpawnDecalAtLocation(Actor, BloodDecal, DecalSize, DecalLocation, RandomDecalRotation, 10.0f);
		}

}

I figured it out. Supposedly I cannot use custom trace channels for single line traces. I can use custom trace channels for multi line trace by channels.

But the only other question for me is that the decal spawns in different rotations relative to the wall and or the floor how can I dynamically change the decal’s rotation at runtime?

Instead of using LineTrace.Rotation(), you can construct your own rotation based on whatever you want the coordinate frame to be.

Typically, you’ll use the normal of the surface hit as “up,” (Z vector) and then you construct the “right” (Y vector) using some combination of cross product and normalization, and then finally construct forward (X vector) using Y cross Z.

For example, you can say that if the absolute of the Z value of the surface normal is greater than 0.8, then use Absolute X as the vector to cross-construct with, else use Absolute Z (up.) You need something like this, because absolute Up is the same as the surface normal of a horizontal floor, and thus a cross product would be zero.

Once you have your reference frame, you can turn it into a rotator to pass into the spawn-decal function.

Example code:

FVector impactNormal = ...;
FVector basis = FVector(0, 0, 1);
if (fabsf(impactNormal.Z) > 0.8) {
    basis = FVector(1, 0, 0);
}
FVector right = CrossProduct(impactNormal, basis).GetUnsafeNormal();
FVector forward = CrossProduct(right, impactNormal);
FBasisVectorMatrix bvm(forward, right, impactNormal, FVector(0, 0, 0));
FRotator theRotation = bvm.Rotator();
1 Like

How would I form a function using this term and what is a cross product?

What would I use to initialize the variable impact normal?

So far this is what I have tried it seems to work. But is unpredictable.

	FVector DecalSize(20.0f, 20.0f, 20.0f);
	FVector ActorRot = GetActorRotation().Vector();
	FRotator RandomDecalRotation = ActorRot.Rotation();
	FVector DecalLocation = TraceEndHit.Location;

A cross product is a very important concept in 3D math. If you’re going to be doing 3D game development, I highly recommend reading up on matrix/vector algebra, dot products, and cross products. Wikipedia is accurate and correct, but perhaps not the best introductory tutorial :slight_smile: Cross product - Wikipedia

To get the surface/impact normal where you’re placing the decal, you have to look at what mecahnism you use to figure out where the place the decal.
For example, if you ray-cast, or place decals in response to collision detection, then you will have a collision result, and that result will have an impact (surface) normal within it.
For example, FHitResult contains an ImpactNormal field.

The code you pasted has a TraceEndHit, so that should contain the impact/surface normal.

Using the rotation of whatever the actor is, is not generally going to be robust, because it has little to no correlation with the actual surface orientation that was hit.

1 Like

But how would I go about creating a function to calculate the cross product?

Does this function exist?

I created a FVector function now I need to pass in 2 parameters then calculate both values. GetUnSafeNormal() is a real function sorry woops.

Would this be a likely way of creating the function?
FVector ABase::CrossProduct(FVector Product1, FVector Product2) { ReturnProduct.CrossProduct(Product2, Product1).Normalize(); return ReturnProduct; }

These are all in the documentation. The top google hit for “unreal engine C++ fvector” is this: FVector | Unreal Engine Documentation

Or you can just select a FVector, and press F12, and jump to the header, where all the functions are declared.

Note that CrossProduct is a static member function.

1 Like

But is the way I am using this function correct?
This is a non static function that calculates the cross product of both vectors and returns a value.

I don’t wanna be that guy but is it possible to find a simpler maybe perhaps less precise answer to this issue? I don’t’ fully understand your example but I really do appreciate your help.

To call a static member function when you’re not in the inner scope of the defining class, name the class explicitly:

FVector right = FVector::CrossProduct(impactNormal, basis).GetUnsafeNormal();

(Note the FVector:: part)

Is there an easier way to “find a consistent orientation that aligns with a particular vector?” No, not really. This is the math that needs to happen.

There might be code in the Unreal libraries that does something very similar to this code, and the function is already there to be used. I haven’t memorized all the Unreal helper functions, but it looks like FRotationMatrix::MakeFromZX() might be helpful.
The “Z” vector should be the surface normal, and the “X” vector should be whatever vector you choose as the reference for clocking (e g, “north” in the above example.)

FVector impactNormal = TraceEndHit.Normal;
FVector basis = FVector(0, 0, 1);
if (fabsf(impactNormal.Z) > 0.8) {
    basis = FVector(1, 0, 0);
}
FMatrix rotation = FRotationMatrix::MakeFromZX(impactNormal, basis);
2 Likes

Thank you man very much I really do appreciate the simplification. You are kind.

Just the fact that there is a function specifically for that is awesome!

I thought I would have to declare a function then write the crossproduct calculation from scratch within the declared function. Just the fact that there is a static member function specifically for cross products is pretty cool.

Note that the correct orientation is FRotationMatrix::MakeFromXZ(-ImpactNormal, MyOtherVector).

Decals project along their local +X axis, so you want it to project into the surface normal, not parallel with it. In decals with normal maps, this matters a lot.

2 Likes