I’m trying to figure out the math behind making an object face another object, only with the rotation pivot being an arbitrary point in space.
I’m trying to get a character’s gun to point at where the crosshair is looking, but the gun itself rotates around some other pivot relative to the barrel.
What I need is the FTransform so the gun points at the blue crosshair but stays fixed around a point.
Normally lookat is very simple but I’m not sure how to do it when there’s a relative transform like this involved.
Well, I ended up solving it! Here’s some code. For now I’m putting it into my game’s GameplayStatics class.
Made sure this works with the most arbitrary of transforms.
Goes into the .h file…
/**
Returns the new transform of OriginTransform such that the RelativeTransform aims at Location down its X axis.
Useful for aiming guns or turrets or other objects around some origin point.
*/
UFUNCTION(BlueprintPure, Category = "Utility")
static FTransform LookatAroundTransform(const FTransform& OriginTransform, const FTransform& RelativeTransform, const FVector& Location);
Goes into the .cpp file…
FTransform UAEGameplayStatics::LookatAroundTransform(const FTransform& OriginTransform, const FTransform& RelativeTransform, const FVector& Location)
{
FVector OriginLocation = OriginTransform.GetLocation();
//Get the transform relative to OriginTransform
FTransform RelOriginTransform = RelativeTransform * OriginTransform;
//Put that transform back to the origin
FTransform RelOriginAtOrigin = RelOriginTransform;
RelOriginAtOrigin.SetLocation(OriginLocation);
//Compute base aim rotation from origin to location such that the up vector points in the direction of AroundOriginAtOrigin's up vector
FQuat AimRotation = FRotationMatrix::MakeFromXZ(Location - OriginLocation, RelOriginAtOrigin.GetRotation().GetUpVector()).ToQuat();
//Get the transform that's aimed at Location from Origin
FTransform RelOriginAtOriginAimedAtLocation = RelOriginAtOrigin;
RelOriginAtOriginAimedAtLocation.SetRotation(AimRotation);
//Put the point that's aiming back to the correct place relative to the origin, but now it's aimed
FTransform OriginAimedAtLocation = RelativeTransform.Inverse() * RelOriginAtOriginAimedAtLocation;
OriginAimedAtLocation.SetLocation(OriginLocation);
FRotator OffsetRot = FRotator::ZeroRotator;
//Lambda to return the rotation offset in a 2D plane
auto PlaneRotOffsetFunc = ](const FVector2D& RelOrigin2D, float DistToLocation) {
//if it's perfectly aligned, we're done
if (RelOrigin2D.Y == 0.f)
{
return 0.f;
}
float RotRadius = RelOrigin2D.Size();
float AngleToRelLoc = RelOrigin2D.X == 0.f
? HALF_PI //90 degrees, optimizing out the need to calculate this if not needed
: FMath::Atan2(FMath::Abs(RelOrigin2D.Y), RelOrigin2D.X);
//normally finding the tangent point to a circle is easy with a right angle, but we may not be at a right angle so find the actual angle
float AngleBetweenRelLocAndStraight = PI - AngleToRelLoc; //180 - AngleToRelLoc
//use law of sines to find other angles in the triangle
//first we have enough info to find the opposite angle that we need, and then knowing that all angles add to 180, we find the one we actually need
float OppositeAngle = FMath::Asin((RotRadius * FMath::Sin(AngleBetweenRelLocAndStraight)) / DistToLocation);
float Res = FMath::RadiansToDegrees(AngleToRelLoc - (PI - OppositeAngle - AngleBetweenRelLocAndStraight));
//flip direction if needed
if (RelOrigin2D.Y < 0.f)
{
Res = -Res;
}
return Res;
};
FVector RelativeLocation = RelativeTransform.GetLocation();
float DistToLocation = FVector::Dist(OriginLocation, Location);
//XY plane (Yaw, rotates around Z axis)
OffsetRot.Yaw = -PlaneRotOffsetFunc(FVector2D(RelativeLocation), DistToLocation);
//XZ plane (Pitch, rotates around Y axis)
OffsetRot.Pitch = -PlaneRotOffsetFunc(FVector2D(RelativeLocation.X, RelativeLocation.Z), DistToLocation);
//Offset the return value by the right amount so the relative transform aims perfectly at the location
OriginAimedAtLocation.ConcatenateRotation(OffsetRot.Quaternion());
return OriginAimedAtLocation;
}