Page MenuHome

The conversion of roll to matrix breaks in some cases
Confirmed, NormalPublicBUG

Description

I refer to this function: source/blender/blenkernel/intern/armature.c :: vec_roll_to_mat3_normalized()

The conversion of roll to matrix breaks when a bone has very small values in x and z. In that case we get a division by very small numbers which can cause a broken bone roll matrix.

details:

As already mentioned in the comments:

due to float precision errors, we can have nor = (0.0, 0.99999994, 0.0)...

I have found other situations where nor == (x, 0.99999994, z) with x and/or z very close to 0, but not exactly 0. However the test (nor[0] || nor[2]) can be true for tiny numbers. So further down in the function we see:

/* If nor is too close to -Y, apply the special case. */
theta = nor[0] * nor[0] + nor[2] * nor[2];

So when x and/or z are tiny numbers, then theta becomes tiny as well. And since we only check for (x or z != 0):

if (theta > THETA_SAFE || ((nor[0] || nor[2]) && theta > THETA_CRITICAL)) ...

You can see that theta can potentially drop below the THETA_THRESHOLD_NEGY_CLOSE if both nor[0] and nor[2] are very close to 0. And this finally leads to potentially very huge numbers for example here (division by almost zero):

theta = nor[0] * nor[0] + nor[2] * nor[2];
bMatrix[0][0] = (nor[0] + nor[2]) * (nor[0] - nor[2]) / -theta;

Because of this i believe it is better to not only check (nor[0] or nor[2] != 0) but also to make sure that (nor[0] * nor[0] + nor[2] * nor[2]) is above THETA_THRESHOLD_NEGY_CLOSE.

Below is a python script that generates a rig that suffers from this bug. Run the script, then switch from edit mode to pose mode and see how "bad bone" disappears. Because its pose bone matrix is totally broken. The problem originates in the super small bone roll values which finally lead to the bug:

import bpy

bone_data = [
    ["good bone", (0.0, 0.0, 1.2038891315460205), (0.0, 0.01536799967288971, 1.3546786308288574), -4.1202467175263267e-16],
    ["bad bone", (0.0, 0.01536799967288971, 1.3546786308288574), (0.0, 0.0, 1.2038891315460205), -1.570796257510665e-07],
]

def make_armature(ctx, name):
    data = bpy.data.armatures.new(name)
    obj  = bpy.data.objects.new(name, data)
    ctx.collection.objects.link(obj)
    ctx.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT')
    parent = None
    for bname, head, tail, roll in bone_data:
        bone = obj.data.edit_bones.new(bname)
        if parent:
            bone.parent = parent
        parent=bone
        bone.head=head
        bone.tail=tail
        bone.roll = roll
    return obj

armobj=make_armature(bpy.context, "armature")

This is a follow up task of the refactoring handled in https://developer.blender.org/D9410

Event Timeline

Gaia Clary (gaiaclary) changed the task status from Needs Triage to Confirmed.Nov 6 2020, 10:36 AM
Gaia Clary (gaiaclary) created this task.

This may be set for Blender developers!

This would be due to the source code!

@champions (schampions) Welcome to developer.blender.org -- here, everything is for Blender developers. Useful feedback is appreciated, but do know that this is not just a forum. It's the central hub for Blender development.

Sybren A. Stüvel (sybren) changed the subtype of this task from "Report" to "Bug".Nov 17 2020, 3:21 PM
Sybren A. Stüvel (sybren) moved this task from Backlog to Bugs on the Animation & Rigging board.