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.
T = 2 * cross(Q.xyz, Vector) # 6 mult
seems like this would be 9 multiplications as well, accounting for 2* each entry and 6 for the cross product itself. – rschwieb Dec 11 '23 at 19:43