0
$\begingroup$

The title is a bit convoluted but I don't know a good way to express it. I am wondering whether this expression that I've worked out for my specific task has been discussed in the past since I have not found any mention of it, and most work tackling similar tasks don't go as far in their optimization.

My specific task is as follows: I have two vectors ReferenceBase and ReferenceTarget, which together implicitly describe a rotation that rotate the former onto the latter. I then have a third vector, just Vector, onto which I want to apply that same rotation. My first intuition was to compute the quaternion that represents this rotation and apply it with quaternion multiplication (using the quaternion(xyz = vector, w = scalar) convention):

def TransferUnitVectorRotation(ReferenceBase, ReferenceTarget, Vector):
    HalfVector = normalize(ReferenceBase + ReferenceTarget)
    Q = Quaternion(cross(ReferenceBase, HalfVector), dot(ReferenceBase, HalfVector)) # 9 mult
    return Q * Quaternion(Vector, 0) * Q.conjugate() # 32 mult

I then expanded and simplified the last line using properties of rotation quaternions to land on the well-known formula (that you can for example find here):

def TransferUnitVectorRotation(ReferenceBase, ReferenceTarget, Vector):
    HalfVector = normalize(ReferenceBase + ReferenceTarget)
    Q = Quaternion(cross(ReferenceBase, HalfVector), dot(ReferenceBase, HalfVector)) # 9 mult
    T = 2 * cross(Q.xyz, Vector) # 6 mult
    return Vector + Q.w * T + cross(Q.xyz, T) # 9 mult

But I then realized that I could expand and simplify even further and express everything in terms of ReferenceBase, HalfVector and Vector. Some more work involving the vector triple product led me to this expression that I have not found anywhere online:

def TransferUnitVectorRotation(ReferenceBase, ReferenceTarget, Vector):
    HalfVector = normalize(ReferenceBase + ReferenceTarget)
    HB = dot(HalfVector, ReferenceBase) # 3 mult
    VB = dot(Vector, ReferenceBase) # 3 mult
    VH = dot(Vector, HalfVector) # 3 mult
    return Vector + ((HB * VB * 2 - VH) * HalfVector - VB * ReferenceBase) * 2 # 7 mult

Bringing the total multiplication count from the original 41 to a mere 16 for the full operation (excluding the first line).

Now this looks an awful lot like an expression involving projections and symmetries, but I have had a hard time researching existing solutions to this task. The best I have found is topics such as this one, which only care about extracting a matrix or quaternion representing the rotation, when I am looking to then immediately apply said rotation to a given vector.

Here is some Python code testing the validity of the derivation.

import numpy as np

def normalize(v):
    return v / np.sqrt(np.dot(v, v))

def QuatConj(q):
    return np.append(-q[0:3], q[3])
def QuatMult(q1, q2):
    return np.append(q1[3] * q2[0:3] + q2[3] * q1[0:3] + np.cross(q1[0:3], q2[0:3]), q1[3] * q2[3] - np.dot(q1[0:3], q2[0:3]))

def TransferUnitVectorRotation(ReferenceBase, ReferenceTarget, Vector):
    HalfVector = normalize(ReferenceBase + ReferenceTarget)
    Rotor = np.append(np.cross(ReferenceBase, HalfVector), np.dot(ReferenceBase, HalfVector))
    return QuatMult(Rotor, QuatMult(np.append(Vector, 0), QuatConj(Rotor)))[0:3]

def TransferUnitVectorRotationFast(ReferenceBase, ReferenceTarget, Vector):
    HalfVector = normalize(ReferenceBase + ReferenceTarget)
    HB = np.dot(HalfVector, ReferenceBase)
    VB = np.dot(Vector, ReferenceBase)
    VH = np.dot(Vector, HalfVector)
    return Vector + 2 * ((HB * VB * 2 - VH) * HalfVector - VB * ReferenceBase)

b = normalize(np.random.rand(3))
t = normalize(np.random.rand(3))
v = np.random.rand(3) * 10
print(TransferUnitVectorRotation(b, t, v))
print(TransferUnitVectorRotationFast(b, t, v))

Has this been discussed anywhere? I didn't exactly do anything ground-breaking so I assume this formula surely is known, but I can't seem to find any mention of anything that looks like it.


$\endgroup$
15
  • 1
    $\begingroup$ Note that a single vector and its target are not sufficient to determine the rotation. If we impose the constraint that the axis of rotation is orthogonal to both the base and target vector, then the solution becomes unique; perhaps this is along the lines of what you had in mind $\endgroup$ Commented Dec 11, 2023 at 19:04
  • $\begingroup$ Yes, in my case I am simply choosing the axis of rotation that happens to be orthogonal to both reference and target, as is shown in the construction of the quaternion. $\endgroup$ Commented Dec 11, 2023 at 19:06
  • $\begingroup$ Well, whatever it is that you are choosing to do with this approach, you should be aware that this is a specific choice that you had made. If your goal is to simply find any rotation that matches base to target (as your description of the task seems to imply), you might be able to find a quicker approach that doesn't satisfy this additional constraint $\endgroup$ Commented Dec 11, 2023 at 19:13
  • 1
    $\begingroup$ I suspect that this is ultimately equivalent to the formula you arrived at by simplifying a vector triple product $\endgroup$ Commented Dec 11, 2023 at 19:19
  • 1
    $\begingroup$ I did not know about the "faster quaternion-vector multiplication," but it is amusing. However, in a lot of applications, one has to apply a single quaternion to many vectors rapidly. When this is the case, it makes more sense to pay a small fixed cost to convert the quaternion to a 3x3 matrix, and then you're only paying 9 multiplications per application to a vector after that. The lesson I learned was the superiority of quaternions really shines in composition, but matrices are still pretty efficient at applying the transformation. $\endgroup$
    – rschwieb
    Commented Dec 11, 2023 at 19:54

0

You must log in to answer this question.