Look At around an arbitrary pivot

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;
}