• News

• Industries

• Learning & Support

• Community

• Marketplace

# How to calculate rotation for a decal on a surface?

I’m trying to correctly position a decal on a surface, that can be oriented in any direction. The decal is constantly repositioned, so I’m not attaching it to any component. Instead, I trace a line from the camera and want to place the decal at the hit location. The position is no problem, since I get the hit location from the line trace. I’m having some issue with calculating the correct rotation though.

Of course I get the hit normal, which I can use:

``````
FRotator SpawnRotation = Hit.Normal.Rotation();

``````

But as far as I can tell, a vector alone does not define a rotation completely, as you could still rotate the decal around the normal vector. I’d like the decal to always have the same rotation, relative to the surface, i.e. if I would rotate the surface around the hit normal, I want the decal to rotate with it, as if it was glued to the surface.

it would be much easier to attach the decal to the surface and let the engine handle all the adjustments

``````
**Spawn Decal Attached**

```

/** Spawns a decal attached to and following the specified component. Does not replicate.
* @param DecalMaterial - decal's material
* @param DecalSize - size of decal
* @param AttachComponent - Component to attach to.
* @param AttachPointName - Optional named point within the AttachComponent to spawn the emitter at
* @param Location - Depending on the value of Location Type this is either a relative offset from the attach component/point or an absolute world position that will be translated to a relative offset
* @param Rotation - Depending on the value of LocationType this is either a relative offset from the attach component/point or an absolute world rotation that will be translated to a realative offset
* @param LocationType - Specifies whether Location is a relative offset or an absolute world position
* @param LifeSpan - destroy decal component after time runs out (0 = infinite)
*/
UFUNCTION(BlueprintCallable, Category="Rendering|Components|Decal", meta=(UnsafeDuringActorConstruction = "true"))
static UDecalComponent* SpawnDecalAttached(class UMaterialInterface* DecalMaterial, FVector DecalSize, class USceneComponent* AttachToComponent, FName AttachPointName = NAME_None, FVector Location = FVector(ForceInit), FRotator Rotation = FRotator::ZeroRotator, EAttachLocation::Type LocationType = EAttachLocation::KeepRelativeOffset, float LifeSpan = 0);

```

``````

EAttachLocation::KeepWorldPosition

I recommend this one
Rama

Yes. But I can’t do that. I need to reposition the decal every frame. I do the line trace from the camera every frame, and put the decal where it hits.

oooh is this like for landscape editing or something ?

I get what you mean now

what is the problem you are encountering exactly?

You just want the decal to have same orientation relative to camera? or does it have to take the surface into account as well?

Rama

It’s a first person game, and the player can place things in the map. These things should be placed on the surface the player looks at. Kind of like how it works in Rust, but I just need a decal, not a ghost mesh (at 0:49):

I am able to correctly position and rotate the decal, so that it is on the surface that the player is looking at:

http://i.imgur.com/0kxi08e.png

However the rotation around the surface normal is not how I want it:

http://i.imgur.com/p4TASuZ.png

Imagine, that cube in the last picture would rotate around the Z axis. I’d like the decal to rotate with it, and always be aligned to the cube’s edges. Just like in the first image.

I hope this makes the problem more clear and thanks for trying to help!

I’m not sure there’s a general solution to this. It depends on your requirements. The desired behavior seems intuitively simple in this case (a square decal applied to the face of a cube), but how should it behave with more complex/irregular geometry?

For the specific example you gave, you could snap the rotation to be relative to one of the object’s local space axes, but that might not give you the desired behavior for different/more complex shapes. Alternatively, you could come up with a system that allows artists to manually define an explicit “tangent direction” for any given surface, and make the rotation relative to that, but then you have to come up with a way to represent it and query for it at runtime, and I think that would also add a lot of content authoring burden.

You’re right, it probably wouldn’t work very well with irregular shapes. The undefined variable I see here is the rotation of the decal around the surface normal. Couldn’t you somehow get that value and set it to zero? I’m not sure that makes sense mathematically or if it would work.

Snapping to local axis sounds like it could work too. I hope there is a more flexible solution though (there might not be :-/ )

The problem is that any rotation around the normal is equally valid as a point of reference. You need to decide what “zero” is before you can rotate something to match it.

I think you’re right. I couldn’t figure out what should be “zero” either. Now I’m trying to implement your local axis snapping idea. Do you have an idea how I could figure out, what component of the rotation (pitch, yaw, roll) I have to set to 0, if I know the surface normal? Let’s just say there are only cubes to deal with.

I think you can rotate the decal correctly by only ever using two of the rotation components, by rotating around the decal’s X and Y axis (if by default the decal would face down). Unfortunately the FVector.Rotator() method seems to return weird results.

Oh, I think I have an idea:

I could take the up unit vector (0,0,1), take the surface normal, project both onto the YZ plane, get the angle. This would tell me one of the rotation components. Doing the same with the XZ plane should give an other component. The last one is zero.

I’m not sure if this is correct, trying now.

I decided to take a stab at this. My approach: I think it’s easier to reason about if you think of it as constructing the decal’s desired coordinate frame from its constituent axis vectors, rather than manipulating Euler rotations.

Let’s start with the “forward” vector since that’s the most obvious. It looks like a DecalActor projects in the direction of its +X axis. Therefore you want the decal’s +X axis to be a vector that opposes the normal of the surface you’re projecting onto.

Then you want to determine which of the object’s local axes most closely corresponds to either your player’s view up or view right axis. This is an arbitrary choice, and the remaining axis can be found as the cross product of the two known axes.

I came up with something like this:

``````
FMatrix hitActorToWorld;
if (hit.Actor.IsValid()) {
hitActorToWorld = hit.Actor->ActorToWorld().ToMatrixNoScale();
} else {
hitActorToWorld = FMatrix::Identity;
}

FVector hitActorX, hitActorY, hitActorZ;
hitActorToWorld.GetUnitAxes(hitActorX, hitActorY, hitActorZ);

const FVector cameraUp = viewRotation.RotateVector(FVector(0, 0, 1));
const float upDotActorX = cameraUp | hitActorX;
const float upDotActorY = cameraUp | hitActorY;
const float upDotActorZ = cameraUp | hitActorZ;

EAxis::Type principalActorAxis;
if (FMath::Abs(upDotActorX) > FMath::Abs(upDotActorY)) {
if (FMath::Abs(upDotActorX) > FMath::Abs(upDotActorZ)) {
principalActorAxis = EAxis::X;
} else {
principalActorAxis = EAxis::Z;
}
} else {
if (FMath::Abs(upDotActorY) > FMath::Abs(upDotActorZ)) {
principalActorAxis = EAxis::Y;
} else {
principalActorAxis = EAxis::Z;
}
}

FVector actorSnappedUp;
switch (principalActorAxis) {
case EAxis::X:
actorSnappedUp = hitActorX * FMath::Sign(upDotActorX);
break;
case EAxis::Y:
actorSnappedUp = hitActorY * FMath::Sign(upDotActorY);
break;
case EAxis::Z:
actorSnappedUp = hitActorZ * FMath::Sign(upDotActorZ);
break;
}

const FVector decalAxisX = -(hit.Normal);
const FVector decalAxisZ = actorSnappedUp;
const FVector decalAxisY = FVector::CrossProduct(decalAxisX, decalAxisZ);

``````

The resulting values can be used similarly to the following:

``````
DecalComponent->SetWorldRotation(FMatrix(decalAxisX, decalAxisY, decalAxisZ, FVector(0, 0, 0)).Rotator());

``````

Neverender, thank you for your help! Much appreciated!! I finally got it to work!