Imagine you are turning a globe, but it’s a special globe that doesn’t have any rigid axis so it’s able to rotate on all 3 as if it were floating. When I highlight a specific point on a sphere (determined by a raytrace impact) I want to be able to drag that point to any other location on the sphere and have the “globe” spin to accommodate. Much like I was taking my finger in real life and moving a globe from one position to the other.
Here is what I have so far. I’m using the LookAtPosition which works fine from some angles, but if I try to grab a point on the side of the sphere it doesn’t drag up and down like it does from the front or back. I know why and it’s because the LookAtPosition doesn’t do anything with the Roll (X) axis…it always returns 0.
This is in VR so I can approach the sphere with a motion controller from any angle or direction but I want the functionality to be the same regardless of the initial point to be dragged. Think more in terms of a floating ball that can be spun front, left, right whatever…as opposed to a globe with a bar going down the center so it can only spin around the Yaw.
//The is the sphere to spin. It's made up of a bunch of separate smaller actors so this is how I have to get it.
APlanet* ParentPlanet = Cast<APlanet>(SelectedTile->GetAttachParentActor());
//Get the new rotation
FRotator TickRotation = UKismetMathLibrary::FindLookAtRotation(ParentPlanet->GetActorLocation(), ControllerLocation);
//This happens on init only when the controller button is pressed to initiate the spin
if (PlanetRotationOffset == FRotator::ZeroRotator)
{
PlanetRotationOffset = TickRotation; //Set the offset
PlanetRotationAmount = ParentPlanet->GetActorRotation(); //Store the current world rotation
}
//Spin the planet
FRotator TargetRotation = TickRotation - PlanetRotationOffset;
ParentPlanet->SetActorRotation(UKismetMathLibrary::ComposeRotators(TargetRotation, PlanetRotationAmount));
Should I be using a rotation matrix instead? And if so, how?
RESOLVED
Based on the accepted answer I was able to get this working. Here is the code I used. A little context first though. I initiate the rotation on the sphere by clicking a button on a motion controller (could easily be a mouse or a different type of controller though). That initial press sets a variable that the button is active and also sets the GripInitV to a ZeroVector. That way that variable is fresh each time we click and hold the button. The rest is taken care of in tick right now but I plan to move that to a timer or something. Tick does a raytrace from the controller to the sphere I want to rotate and that is all that happens before this code runs.
//This is the actor I am rotating
APlanet* ParentPlanet = Cast<APlanet>(SelectedTile->GetAttachParentActor());
//Initialize vars
FVector ImpactV = HitData.ImpactPoint;
FVector PlanetCenterV = ParentPlanet->GetActorLocation();
if (RightGripStickyLocation == FVector::ZeroVector)
{
//This only gets set the first tick after the button is pressed
RightGripStickyLocation = ImpactV; //The start location
PlanetRotationAmount = ParentPlanet->GetActorRotation(); //The existing rotation
}
//Create the actual drag start and end vectors
FVector DragStart = RightGripStickyLocation - PlanetCenterV;
FVector DragEnd = ImpactV - PlanetCenterV;
//Build the rotation quaternion
FVector RotationV = FVector::CrossProduct(DragStart, DragEnd).UpVector;
FQuat RotationQuat = FQuat::FindBetweenVectors(DragStart, DragEnd);
float RotationAngle = FVector::DotProduct(DragStart, DragEnd) * DeltaTime;
RotationQuat.ToAxisAndAngle(RotationV, RotationAngle); //Set the axis and angle
//Rotate the sphere in world space
FTransform TargetTransform = ParentPlanet->GetTransform();
TargetTransform.SetRotation(RotationQuat);
TargetTransform.ConcatenateRotation(PlanetRotationAmount.Quaternion());
TargetTransform.NormalizeRotation();
ParentPlanet->SetActorTransform(TargetTransform);