SkeletalMesh - Visual Mesh Rotation Precision Issues?

I’m having a small but irritating issue with the rotation of my actor, whereby there seems to be a lack of precision of the visual mesh relative to it’s actual component transform. Here’s a video of the issue to show what I mean:

Notice how as the rotation slows down, the reticle ‘drifts’ slightly in that direction still. The vehicle mesh should follow it perfectly but it doesn’t. The Reticle and Camera is ‘attached’ to the vehicle transform and also seems to update just fine - but the vehicle mesh stops rotating before the camera does and before the rotation has actually finished.

When the vehicle is moved again, the mesh suddenly ‘jolts’ to the correct rotation. It’s as if the engine isn’t updating the visual mesh transform all the time and is trying to cull the updates somewhere - which I need to change because the effect of that is horrible.

I never noticed this problem before when my movement used the physics engine. My movement system has been rewritten and is now based on a similar one to CharacterMovement, whereby all the ‘physics’ are calculated in the component and applied at the end. Nothing here is actually simulating physics, and the actual values of the transforms and rotations are 100% verified and correct.


This is where I actually apply my rotation (I’ve tried a few different methods, none seem to make a difference). At first I thought it was because of the precision of floats, and multiplying ‘Omega’ by ‘DeltaSeconds’ would yield too small of a value to store accurately, but that wasn’t the issue either (I also tried clamping FPS to 30 to see if that helped, it didn’t).



void UBZGame_VehicleMovement::ApplyVelocities(const float DeltaSeconds)
{
	// Check Floor

	const FVector Delta = Velocity * DeltaSeconds;
	const FVector ScaledOmega = Omega * DeltaSeconds;
	const FQuat OmegaQuat = FQuat(FVector::ForwardVector, ScaledOmega.X) * FQuat(FVector::RightVector, ScaledOmega.Y) * FQuat(FVector::UpVector, ScaledOmega.Z);

	const FQuat FinalRotation = OmegaQuat * UpdatedComponent->GetComponentQuat();
	FHitResult MoveHit = FHitResult(1.f);
	
	SafeMoveUpdatedComponent(Delta, FinalRotation, true, MoveHit);


To prove that the actual component transform is correct, this is the way the reticle position is calculated. Notice how it uses the actual Actor Rotation to determine where to place it, meaning that the actor rotation values ARE correct, it’s just that the Skeletal Mesh isn’t visually following it. The SkeletalMesh is the RootComponent of the actor.



FVector UBZGame_UnitComponent::WeaponConvergeLocation() const
{
	const APawn* PawnOwner = Cast<APawn>(GetOwner());
	if (PawnOwner)
	{
		const IBZGame_GameObjectInterface* OwningGO = Cast<IBZGame_GameObjectInterface>(PawnOwner);
		ASSERTV_WR(OwningGO != nullptr, FVector::ZeroVector, TEXT("WeaponConvergeLocation: Invalid GO"));

		const FVector EyepointLocWS = OwningGO->GetRootMesh()->GetSocketLocation(TEXT("HP_Eyepoint"));

		const FVector AimRotation = FRotator(PawnAimOffsets.Y, 0.f, 0.f).Vector();
		const FVector AimDir = PawnOwner->GetActorRotation().RotateVector(AimRotation);

		FVector ConvergeLocation = EyepointLocWS + (AimDir * CalcWeaponRange());
		return ConvergeLocation;
	}

	return FVector::ZeroVector;
}


Might it be some network quantization going on after you set the value (based on the replication settings of the actor)?

Nope, this is a standalone game only. Still doesn’t make sense that the mesh transform would be right but the visual mesh isn’t

Tried setting the component update flag?

GetMesh()->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::AlwaysTickPoseAndRefreshBones;

In the constructor of the actor when creating the component. Long shot, but might be worth a try.

No effect. The aim location is actually determined from the bone rotation of the mesh, so actually the bones / actual transform values are updating fine - but the camera and mesh isn’t. For reference, this is how I get the aim-location in World Space:



FVector UBZGame_UnitComponent::WeaponConvergeLocation() const
{
	const APawn* PawnOwner = Cast<APawn>(GetOwner());
	if (PawnOwner)
	{
		const IBZGame_GameObjectInterface* OwningGO = Cast<IBZGame_GameObjectInterface>(PawnOwner);
		ASSERTV_WR(OwningGO != nullptr, FVector::ZeroVector, TEXT("WeaponConvergeLocation: Invalid GO"));

		const FVector EyepointLocWS = OwningGO->GetRootMesh()->GetSocketLocation(TEXT("HP_Eyepoint"));

		const FVector AimRotation = FRotator(PawnAimOffsets.X, 0.f, 0.f).Vector();
		const FVector AimDir = PawnOwner->GetActorRotation().RotateVector(AimRotation);

		FVector ConvergeLocation = EyepointLocWS + (AimDir * CalcWeaponRange());
		return ConvergeLocation;
	}

	return FVector::ZeroVector;
}


As you can see, the rotation is determined from the rotation of the object mesh - so the transform itself does update, but the camera and all meshes don’t. The ‘Root Mesh’ is the object the rotation is applied to (a USkeletalMeshComponent), The camera and first-person mesh are attached to the root mesh.

I believe the issue is deeper rooted in USceneComponent, possible here in UpdateComponentToWorldWithParent(), but I’m going to have to do further testing to find out.



	// If transform has changed..
	bool bHasChanged;
	{
		//QUICK_SCOPE_CYCLE_COUNTER(STAT_USceneComponent_UpdateComponentToWorldWithParent_HasChanged);
		bHasChanged = !ComponentToWorld.Equals(NewTransform, SMALL_NUMBER);
	}
	if (bHasChanged)
	{
		//QUICK_SCOPE_CYCLE_COUNTER(STAT_USceneComponent_UpdateComponentToWorldWithParent_Changed);
		// Update transform
		ComponentToWorld = NewTransform;
		PropagateTransformUpdate(true, UpdateTransformFlags, Teleport);
	}
	else
	{
		//QUICK_SCOPE_CYCLE_COUNTER(STAT_USceneComponent_UpdateComponentToWorldWithParent_NotChanged);
		PropagateTransformUpdate(false);
	}


It might be that the tolerance (SMALL_NUMBER) causes precision errors when comparing the transforms, I’m not sure. I’ve recently realised that if I move my mouse slowly enough, I can get the reticle to the edge of the screen without getting a single transform update.

Looking into this even further, it appears the PropagateTransformUpdate is responsible for updating the Bounds and Render Transform of the object. I’m assuming this is done because there’s no point re-calculating this for objects that never move (which could be hundreds if not thousands of extra updates and bounds calculations), so this is some form of optimization.

It appears I’ve found a need to have this exposed on a per-scene component basis, so that some objects that may update very minutely can have transform update forced.

Looks like I’ m in the right ballpark. Adding these lines to my custom Movement Component stops the precision issues (added after SafeMoveUpdatedComponent is called to actually change the transform). The problem now of course is most of the time I’m doing two Bounds, Render Transform and Child Transform updates for each object.



        // Force-Update Transform!
	UpdatedComponent->UpdateBounds();
	UpdatedComponent->MarkRenderTransformDirty();
	UpdatedComponent->UpdateChildTransforms(EUpdateTransformFlags::PropagateFromParent, ETeleportType::None);


It’s odd, because PropagateTransformUpdate calls these things even if the transform DOESN’T change… so I really am getting lost now!