Extremely jittery movement when rotating actors in a hierarchy

For an arch viz project, I created a script that generates folding doors to a given size and door count, and animates them procedurally. The folding door is built as a chain of pairs - each pair is its own actor. Each subsequent pair is attached to its neighbour, so it moves along with it. The video below will show the concept, and the problem.

For some reason, when rotating actors in a hierarchy, the resulting movement is incredibly jittery. The problem is less pronounced when I fold one pair at a time, but it’s still there. The hierarchy of a pair is built as such:

Folding pair hierarchy.png

The hinges are located where you would expect them to be - the master and slave hinge are in the same location, between the two doors. Each pair is attached by its “hinge to previous” to the preceding pair’s “hinge to next”. The following bit of code from the folding pair actor’s Tick() is all that is done to animate the doors:


FQuat angle = FQuat::Slerp(FQuat::Identity, FRotator(0, 90, 0).Quaternion(), _erpPos);

_hingeFromPrevious->SetRelativeRotation(angle);
_hingeToNext->SetRelativeRotation(angle);
// Mirror rotation on slave hinge.
_slaveHinge->SetRelativeRotation(FRotator(0, -FMath::Lerp(0,90,_erpPos) * 2, 0));

So, who knows what’s up?

for the first sight, it would be better to include actual frame time, to have a speed independent from FPS. there are 2 nice functions for this purpose available via FMath:: too:

/** Interpolate float from Current to Target with constant step */
static CORE_API float FInterpConstantTo( float Current, float Target, float DeltaTime, float InterpSpeed );

/** Interpolate float from Current to Target. Scaled by distance to Target, so it has a strong start speed and ease out. */
static CORE_API float FInterpTo( float Current, float Target, float DeltaTime, float InterpSpeed );

Hi sivan, thanks for the quick reply. To clarify, frame time is already taken into account by the actor which spawns the pairs. This is its tick:


for (auto pair : _foldingPairs)
{
	pair->SetInterpolationPosition(_erpPos);
}

if (!FoldingData)
	return;

if (_targetState)
{
	_erpPos += GetWorld()->GetDeltaSeconds() / FoldingData->AnimationSettings.Duration;
}
else
{
	_erpPos -= GetWorld()->GetDeltaSeconds() / FoldingData->AnimationSettings.Duration;
}

_erpPos = FMath::Clamp(_erpPos, 0.f, 1.f);

For reference, this is what the same code looks like with just a single pair, to demonstrate that the problem only occurs when actors are rotating further up in the hierarchy. The door on the left rotates smoothly, while the one on the right, which is attached to the other one, is already quite shaky. The shakiness only amplifies with the amount of steps down the hierarchy, as seen in the video above.

Although i’m not sure the fix for this, have you considered just creating the door in maya and making an animation for it, then calling the animation from C++? You can control speed etc from C++ if needed, and it would save you writing all this lerp code yourself

Hi Reuben, for the requirements of the application the doors have to be created procedurally. I just draw a bounding box in the editor, and define how many doors etc in a data asset.

I want to say that this is probably floating point inaccuracy, and as you make the chain longer and longer the inaccuracies are increased. You might want to try doing the multiplication / math in doubles - then converting back to float as you apply it to the door.

It also might be worth setting world rotation rather than relative rotation, to avoid additional error between the transforms. Just set the doors to use Absolute rotation. Should also make life easier.

Hi Jamsh, thanks for the reply. However, I don’t really see how this could be a floating point inaccuracy. I don’t propagate the rotation from one door to the next - there is no interaction between the pairs on my end. The main actor which spawns the pairs calculates the interpolation position each frame, and supplies this value to all pairs. The pairs only use this value to slerp the rotation of their own two doors. The pairs move because they are attached to each other.

Same goes for relative rotation. This is a perfect case to use relative instead of world rotation, since it’s just a rotation between 0 and 90 degrees on the Z axis for each door, nothing more. Taking world rotation into account would only complicate things.

I had a problem like this a few years ago, so I’m not sure if it’s still relevant. Back then the solution was to set up tick prerequisites so the parts will be updated in the correct order (left to right in your video)

Interesting suggestion, I wasn’t yet aware of tick prerequisites and tick groups in Unreal. Sadly, setting up the prerequisite chain (first pair waits on controlling actor, second pair waits on first pair, etc) did not resolve the issue.