NaN possible in USkeletalMeshComponent::UpdateRigidBodyScaling

Hi, we were seeing a case of NaNs causing extreme performance degradation (and possibly crashes) which I tracked back to USkeletalMeshComponent::UpdateRigidBodyScaling where it divides by PrevRootRigidBodyScale (which can be ZeroVector).

We ended up changing the condition right above that line from

`if (!(PrevRootRigidBodyScale - SceneRelativeScale).IsNearlyZero())`

to

`if (!(PrevRootRigidBodyScale - SceneRelativeScale).IsNearlyZero() && !PrevRootRigidBodyScale.Equals(FVector3f::ZeroVector))`

which prevents it from creating NaN values. That said, I’m not sure if this will have side effects since the body scale simply will not be updated unless the function is executed a second time with a non-zero PrevRootRigidBodyScale. Perhaps there’s a better way of calculating the scale that doesn’t require a division at all so the inner scope can still be executed. Regardless, it appears that the case of the vector being zero is not handled in 5.7 or Main (we’re seeing it in a 5.6.1 project), so I wanted to bring this to your attention.

[Attachment Removed]

Hi Andrew,

Thank you for the report. I was able to repro a situation where USkeletalMeshComponent::UpdateRigidBodyScaling() generates NaNs for RelativeScale3DLocal as you described. The repro is basically to set the relative scale of a simulated skeletal mesh component to (0,0,0) and then back to some non-zero uniform scale value while in simulate/play mode. This also triggers an ensure() inside function FBodyInstance::UpdateBodyScale().

I am preparing an internal bug report for this issue now, and I’ll get back to you soon with a tracking number for it.

Note that, up to UE 5.5, skeletal mesh physics did not get updated when the component’s scale changed. This behavior was added on January 2025 by CL 39190335 (GitHub commit 08621a0), and apparently it does not work correctly when the component gets zero scale temporarily. There is also a “TODO” comment in the code warning that the current approach can also lead to drift when the scale is continuously varied. If you don’t need physics to be updated when the scale changes, you can disable this behavior with CVar “p.UpdateRigidBodyScaling”.

About your workaround, I think a better approach would be to prevent running that inner code block when “SceneRelativeScale.IsNearlyZero()”. This would prevent PrevRootRigidBodyScale from ever getting set to zero (a value which makes any later scale unrecoverable by accumulating with a multiplication or division). It would also prevent some assertions that can also be triggered in physics code for zero-volume bodies.

Let me know if the CVar or workaround I mentioned works for you.

Best regards,

Vitor

[Attachment Removed]

That also sounds like a reasonable check assuming that scaling the bodies to zero should never be allowed. That said, since a value of zero for GetRelativeScale3D() is technically allowed, I assume that means the body scale would remain set to the previous value (despite the visual zero scale) which would be unexpected from a content authoring perspective. (Of course, getting into an unrecoverable zero scale state is also unexpected, but happens to be a non-issue in the particular scenario we encountered.)

Given this would also be undesirable (but preferable to NaNs), perhaps it would be better to not allow the PrevRootRigidBodyScale to get smaller than the tolerance for IsNearlyZero() even if the value returned by GetRelativeScale3D() is zero? That way they’d at least be somewhat close in scale for the duration it’s zero.

[Attachment Removed]

Indeed, the fix I proposed was more of a safeguard to avoid problems arising from a scale of zero, but it does imply that zero scale is not actually supported, so it should also at least log a warning to the console.

Also, I agree that clamping the result of GetRelativeScale3D() to a small nonzero value would be preferable to keep the resulting behavior as close as possible to expectations. Just note that this might have to be higher than the tolerance used by IsNearlyZero(), because repeatedly multiplying and dividing by a very small number can probably make the resulting scale drift significantly from the correct value. Unfortunately, this is an inherent flaw in the current approach used in USkeletalMeshComponent::UpdateRigidBodyScaling(), and known to the devs as per the following comment in the code:

//TODO(Chaos): Using current scale to compute for desired scale may lead to drift when scales are continuously varied. 
// It may help if the scale can be pulled from somewhere else.

In any case, here’s the tracking number for the bug report I just filed: UE-365189. An official fix might take a while, since solving this robustly will likely require changing the current approach.

All the best,

Vitor

[Attachment Removed]

Sounds good, we’ll just modify that locally for the time being. Thanks.

[Attachment Removed]