Possibly Buggy Function

EDIT: SOLVED! (see comments)

This is related to https://blenderartists.org/forum/showthread.php?446038-Slightly-Buggy-Camera-Script

I have this function:


#Given two vectors, 'a' and 'b',  generate a rotation matrix that maps 'a' onto 'b'
def VecToVecMatrix(a,b):
   angle = a.angle(b)
   perp = a.cross(b)
   if a.cross(perp).dot(b) < 0:
      angle = -angle
   rotmat = mathutils.Matrix.Rotation(angle, 3, perp)
   return rotmat

when I input two antiparallel vectors for a and b, the script usually returns (GOOD)
-1 0 0
0 -1 0
0 0 1

however, sometimes it randomly returns (NOT GOOD)
1 0 0
0 1 0
0 0 1

Does anyone know the cause of this issue?

type [ C O D E ] without spaces to start a code block

[/C O D E]

to end

code code code

Thanks, edited post.

Why are you calculating two cross products?


perp = a.cross(b)

perp is othogonal to a and b

then you calculate


a.cross(perp) # which basically is: a.cross(a.cross(b))

This will result in a vector orthogonal to a and perp. This is a vector parallel to b. Due to the order of operation you get -b.

You can shorten your formula to:


   angle = a.angle(b)
   if b.dot(-b) < 0:

This is in semantics a over/under check on the plane orthogonal to b.

But this does not help you solve your problem.

Are you going to determine the rotation matrix that turns from vector a to b?

Thanks for the input. I didn’t actually write this function myself, I’m just trying to debug it so I don’t totally understand exactly what it’s supposed to do (especially since my linear algebra is pretty rusty).

Anyway, I think a.cross(perp) is only equal to -b (in direction, not necessarily magnitude) when a and b are separated by 90 degrees (pi/2). Or maybe my math is wrong?

I believe that one of the functions (cross or dot or maybe both) returns some nonsensical value when fed two antiparallel vectors or fed a zero vector (since if b = -a, a.cross(b) = 0 right?).

My issue may also have something to do with double/floating point precision.

I’ll keep looking into it.

My math is rusty too. So I tested with a few vectors. You are right, I used the wrong vectors to test with ;).

It is true, when a and b are not orthogonal to each other a.cross(a.cross(b)) is orthogonal to a and a.cross(b), which is not b or -b.

Something I just noticed. The matrices from post#1 do not look like rotation matrices. The first one mirrors the object at two axis (x and y). The second one does nothing (what you get when the vectors are parallel)

Can you tell what the input vectors are?

Something more:

These formulas are used to determine if a is left of b as angle() just returns the smallest angle (not the direction).

This is a valid approach, but mathutils.Rotation() receives the rotation axis (perp). This axis already provides a direction. It looks like the previous calculation cancels this direction, but I’m not really sure about that.

I somehow managed to brute-force solve this lol. I still don’t really know what the problem was, but I just force-corrected the matrix when a and b were antiparallel.


def VecToVecMatrix(a,b):
    angle = a.angle(b)
    perp = a.cross(b)
    if a.cross(perp).dot(b) <= 0:
        angle = -angle
        
    rotmat = mathutils.Matrix.Rotation(angle, 3, perp)
    if a.dot(b) < 0:
        if rotmat[0][0] > 0:
            rotmat[0][0] *= -1
        if rotmat[1][1] > 0:
            rotmat[1][1] *= -1
    
    return rotmat


If you want explanation…

The cross product of parallel or anti-parallel 3D vectors is actually (0,0,0). This is useless for most 3D math involving cross products. It would not surprise me at all if Blender is using some weird, poorly tested NAN structure to deal with this special case.

You can accept the risk of artifacts (like if you’re writing shaders, where that will never in reality happen) or you can add code to deal with that special case.