I am prototyping a hover ship game and am currently able to adjust the ship orientation to different surface normals from a static mesh using quaternions (see screenshot). Now, I would like to modify the C++ code to use barycentric coordinates so I can smoothly transition between the surfaces. The basic idea would be to use a downward ray cast and then:
(1) get the impact face
(2) get face vertices and vertex normals
(3) use the values to calculate the barycentric coordinate
(4) make a new quaternion to orient the ship
Is this a reasonable approach, and can you suggest the optimal way to code it?
// Called on tick
void AShip::OrientOnSurface(float DeltaSeconds)
{
const int TraceStartFactor = 1;
const int TraceLength = 1000;
const float HoverHeight = 150.0f;
FQuat ActorQuat = GetActorQuat();
FVector ActorDownVector = ActorQuat.RotateVector(FVector(0, 0, -1));
FVector ActorUpVector = ActorQuat.RotateVector(FVector(0, 0, 1));
FVector ActorRightVector = ActorQuat.RotateVector(FVector(0, 1, 0));
FVector ActorForwardVector = ActorQuat.RotateVector(FVector(1, 0, 0));
FVector ActorLocation = GetActorLocation();
FVector Start = ActorLocation + ActorDownVector * TraceStartFactor;
FVector End = ActorLocation + ActorDownVector * TraceLength;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
QueryParams.bReturnFaceIndex = true;
FCollisionResponseParams ResponseParams;
FHitResult HitDetails;
GetWorld()->LineTraceSingleByChannel
(
HitDetails,
Start,
End,
ECC_Visibility,
QueryParams,
ResponseParams
);
// If ray cast downward hits a surface
if (HitDetails.bBlockingHit)
{
FVector ImpactNormal = HitDetails.ImpactNormal;
FVector ImpactPoint = HitDetails.ImpactPoint;
DrawDebugDirectionalArrow(GetWorld(), Start, ImpactPoint, 40, FColor::Red, true, 5);
// Set hover height above surface
SetActorLocation(ImpactPoint + ImpactNormal * HoverHeight);
// Set rotation to match surface normal
// NOTE: The goal is to update this code by getting the face index from the ray hit, get face vertices and vertex normals, then calculate barycentric coordinates.
FVector cp = FVector::CrossProduct(ActorUpVector, ImpactNormal);
float dp = FVector::DotProduct(ActorUpVector, ImpactNormal);
float s = FMath::Sqrt((1.0 + dp) * 2.0);
float rs = 1.0 / s;
FQuat q(cp.X * rs, cp.Y * rs, cp.Z * rs, s * 0.5);
q = q * ActorQuat;
q.Normalize();
SetActorRotation(q);
};
}