Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Obi Bones With Stretch Bones Get NaN Values
#1
As title I'm trying to set up bones to our character, but it seems that at high changes the bones break completley to NaN Values. If I turn stretch bones off, the bones will still break but be in "no active" mode, to show T pose bones transforms.

Video to show issue
https://drive.google.com/drive/folders/1...x1lAva8Hga

The only way I've found to get a "completley safe" case is to set up "Use Limits" on the Solver to restrict the simulation area.

Is this a bug? It feels a bit contradictory to the "unconditionally stable" from documentation https://obi.virtualmethodstudio.com/manu...gence.html.

Thanks for input.
Reply
#2
Might have had wrong sharing permission on link, should work now.
Reply
#3
(19-08-2026, 02:27 PM)Jawsarn Wrote: As title I'm trying to set up bones to our character, but it seems that at high changes the bones break completley to NaN Values. If I turn stretch bones off, the bones will still break but be in "no active" mode, to show T pose bones transforms.

Video to show issue
https://drive.google.com/drive/folders/1...x1lAva8Hga

The only way I've found to get a "completley safe" case is to set up "Use Limits" on the Solver to restrict the simulation area.

Hi!

I'm unable to reproduce this, rotating the solver and/or the bone root at high speeds doesn't seem to break anything with the bone settings seen in your video.

Note that non-zero world space linear/angular inertia values (found in the ObiSolver component) can cause instability when rotating the solver extremely fast, since these inject centrifugal/coriolis inertial forces at the velocity level that may become arbitrarily large.

(19-08-2026, 02:27 PM)Jawsarn Wrote: Is this a bug? It feels a bit contradictory to the "unconditionally stable" from documentation https://obi.virtualmethodstudio.com/manu...gence.html.

"Unconditionally stable" means the simulation cannot diverge due to the timestep length being too large, as it is usual with traditional velocity/force/impulse based methods. This doesn't mean the simulation cannot break due to other causes unrelated to numerical integration.

kind regards,
Reply
#4
Upon further testing, could reproduce this using the Burst backend with world-space wind enabled in the solver, regardless of the world-space inertia values used.

The problem lies in the aerodynamic constraints: when using too high drag/lift values (you're using 3 and 1, the defaults being 0.05 and 0.02), the relative velocity between the particles and the surrounding air can induce very high acceleration.

Could you verify that disabling aerodynamics on your ObiBone circumvents the issue?

Will fix this asap, the share a patch. Thanks for reporting!
Reply
#5
Found the cause: in ApplyInertialForcesJob, we calculate a solver-space wind intensity due to solver rotation/translation in world space if the solver's wind space is set to "world".

Rigidbodies in Unity have a maximum angular velocity, but rotating the solver transform in Obi does not. Typically, the solver is driven by the character's rigidbody so angular velocity stays within a safe range. But manually rotating the solver transform allows for very high angular velocity values, resulting in hurricane-like wind values for particles far enough from the center of rotation (eg. float4(-8505.487E12f, 0f, -43300.07E19f, 0f)). This eventually leads to "Inf" relative velocities between particles and wind, that when multiplied by 0 attack angle in aerodynamic constraints yields NaN.

The safest solution is simply to clamp the wind magnitude to a sane value that does not allow the relative velocity between wind and particles to get close to infinite:

Code:
float maxMagnitude = 100; // feel free to use your own value
float magnitude = math.length(wind[i]);
if (magnitude > maxMagnitude)
    wind[i] = wind[i] / magnitude * maxMagnitude;

The Execute() method of ApplyInertialForcesJob.cs after this modification should look like this:

Code:
public void Execute(int index)
{
    int i = activeParticles[index];

    if (invMasses[i] > 0)
{
float4 euler = new float4(math.cross(eulerAccel.xyz, positions[i].xyz), 0);
float4 centrifugal = new float4(math.cross(angularVel.xyz, math.cross(angularVel.xyz, positions[i].xyz)), 0);
float4 coriolis = 2 * new float4(math.cross(angularVel.xyz, velocities[i].xyz), 0);
float4 angularAccel = euler + coriolis + centrifugal;

velocities[i] -= (inertialAccel * worldLinearInertiaScale + angularAccel * worldAngularInertiaScale) * deltaTime;
}

    wind[i] = ambientWind;

    if (inertialWind)
    {
        float4 wsPos = inertialFrame.frame.TransformPoint(positions[i]);
        wind[i] -= inertialFrame.frame.InverseTransformVector(inertialFrame.VelocityAtPoint(wsPos));

        float maxMagnitude = 100;
        float magnitude = math.length(wind[i]);
        if (magnitude > maxMagnitude)
            wind[i] = wind[i] / magnitude * maxMagnitude;
    }
}

Why not clamp the solver's angular velocity instead, like Unity does with rigidbodies? because linear velocity at a point due to rigid rotation depends on the cross product between the point position relative to the center of rotation and the angular velocity, so its magnitude scales with the distance from the point to the center. Rigidbodies are typically small and points in the rigidbody not far from its center, but a particle may be arbitrarily far from the solver’s center. Clamping the angular velocity would still allow for extremely high wind values at points far away from the solver's center, so it's safer to clamp the wind magnitude directly.

let me know if I can be of further help,

kind regards
Reply
#6
Hi, thanks for looking into this!

Alright, I wonder why not do it in the AerodynamicConstraintsBatchJob and clamp the final velocity towards the SolverParameters.maxVelocity then? Will it make the job less efficient with branching?

Thanks
Reply
#7
(24-08-2026, 04:06 PM)Jawsarn Wrote: Hi, thanks for looking into this!

Alright, I wonder why not do it in the AerodynamicConstraintsBatchJob and clamp the final velocity towards the SolverParameters.maxVelocity then? Will it make the job less efficient with branching?

Thanks

The problem is that clamping the final velocity is too late, since NaN propagates down the stream from the first operation involving it. Any arithmetic operation involving a NaN operand results in NaN. By the time we’ve calculated the aerodynamics-adjusted velocity (from a NaN relative velocity, in turn calculated from Inf wind) it is already NaN, and clamping NaN yields NaN.

So the thing that should be clamped is the value *leading* to the first Inf/NaN (in this case, the wind value, calculated from the cross product between solver angular velocity and particle position).

Cheers,
Reply