June 19, 2012

Rotation: algebra

An earlier post on rotational pivoting used a correct yet misleading algebraic slight of hand.  Given the relationship

(1)    vecA = vecB * rotQ

the post solved for vecB by "dividing" both sides of the equation by rotQ:

(2)    vecB = vecA / rotQ

The LSL compiler accepts this expression, however, the concept of 'divide by a rotation' opens many doors to confused thinking.  When solving complex rotational problems, it is safer to think in terms of multiplication by inverse rotations.  

The inverse of rotQ is the rotation that undoes rotQ.   In LSL this inverse is designated using the expression "ZERO_ROTATION / rotQ".   By the meaning of 'inverse'

(3)    rotQ * ( ZERO_ROTATION / rotQ ) = ZERO_ROTATION
(4)    ( ZERO_ROTATION / rotQ ) * rotQ = ZERO_ROTATION   

To solve for vecB using inverse rotations, right side multiplying each side of (1) by the inverse of rotQ

(5)    vecA * (ZERO_ROTATION / rotQ) = (vecB * rotQ) * (ZERO_ROTATION / rotQ)

Calculation precedence does not matter when multiplying vectors and rotation.  Thus the terms on the right side of (5) can be regrouped:

(6)    vecA * (ZERO_ROTATION / rotQ) = vecB * ( rotQ * (ZERO_ROTATION / rotQ) )

Using (3) on the right side of (6), (6) becomes
(7)    vecA * ( ZERO_ROTATION / rotQ ) = vecB * ZERO_ROTATION

Since ZERO_ROTATION means not rotating, and since not rotating vecB leaves it unchanged

(8)     vecB * ZERO_ROTATION = vecB.

and therefore

(9)    vecB = vecA * ( ZERO_ROTATION / rotQ )

If the proof of (3) and (4) seems to be a trivial rearrangement of terms, then you may be thinking in terms of numeric algebra, not vector and rotation algebra.  The use of inappropriate numeric algebraic rules is the cause of many types of logical errors when attempting to solve rotational algebra problems.

In (9) the expression "ZERO_ROTATION / rotQ" is a compiler syntactical trick.  It should not be mentally parsed into the items ZERO_ROTATION, /, and rotQ.  The compiler consumes it as a unit and emits the operation "compute inverse of rotQ".

In equation (2), the expression "vecA / rotQ" is also a compiler syntactic trick and should not be mentally parsed into items.  The LSL compiler treats "vecA / rotQ" as if it were "vecA * ( ZERO_ROTATION / rotQ )".  Thus (2) and (9) express the same result.

  • The formal name for the rule (vecA * rotP) * rotQ = vecA * (rotP * rotQ) is associativity.  This rule means that the expression vecA * rotP * rotQ is unambiguous. 
  • The order of rotations matters.  vecA * rotP * rotQ != vecA * rotQ * rotP unless the rotations have the same axis.  This formal name for this property is non-commutative. 
  • Rotation is distributive when applied to the sum of two vectors.  ( vecA + vecB ) * rotQ = vecA * rotQ + vecB * rotQ

  • When multiplying vectors and rotations, the rotation are always on the right side.  This is a syntactical convention based on desiring vectors to be represented by a horizontal row of numbers in matrix algebra. 

  • The expression rotQ * vecA is meanless.
  • You cannot solve (1) for rotQ = vecA / vecB because a vector does not have an inverse.  This limitation reflects the existence of an infinite number of rotations that carry vecB to vecA.  However, there is a unique smallest angle rotation from vecB to vecA.  llRotBetween(vecB, vecA) gives you this smallest rotation.
  • You may fall into serious logical trouble if you write expressions like vecA / rotP / rotQ because rotational / is not associative, that is (vecA/rotP)/rotQ != vecA*(rotP/rotQ).

  • For more discussion of the rules of rotational algebra consult any text book on computer graphics mathematics and seek the chapters on spatial rotation.  If you consult non-CG text books you will discover the the rest of the world represents vectors as columns and uses left side rotational multiplication.
  • For a more formal discussion of non-commutative algebra read: Introduction to Lie Algebra by Erdmann and Wildon.

    • A common bit of advice is to remark that rotations are quaternions (they are not) and thus the inverse of rotQ can be created using rotQ.s = -rotQ.s.  LSL does indeed represent rotations by quaternions and this technique does work.  However, since the LSL compiler translates the abstract expression "ZERO_ROTATION / rotQ"  into the representation specific operations "tmp = rotQ; tmp.s = -tmp.s;", a laying-of-hands on the internals of a quaternion does not make a script more efficient.