Difference between revisions of "Rotating an object about local angles/axes"

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search
imported>ThePhilanthropy
m
imported>ThePhilanthropy
Line 2: Line 2:
It is possible to rotate an object about local angles by first specifying the local angles, then extracting the world angles from the local rotation and applying those to the object.   
It is possible to rotate an object about local angles by first specifying the local angles, then extracting the world angles from the local rotation and applying those to the object.   


==Example==
Any local rotation is dependent on the order in which the individual rotations are applied. This is true because the local axes are dependent on each other.
 
Because of this, it is important to specify the axis order prior to calculating the world rotation.
 
An axis order of XYZ means that the X rotation is applied first, then the Y and lastly the Z rotation.
 
 
These declarations are needed:
These declarations are needed:
<pre>
<pre>
;the object one wishes to rotate
;the object one wishes to rotate
   ref myObject
   ref myObject
;specifes order of rotations.
  float axisOrder
  ;1 = xyz
  ;2 = xzy
  ;3 = yxz
  ;4 = yzx
  ;5 = zxy
  ;6 = zyx




Line 30: Line 44:
   float cz
   float cz


;the cells of the rotation matrix
  float r11
  float r12
  float r13
  float r21
  float r22
  float r23
  float r31
  float r32
  float r33


;temporary arguments for trigonometric functions
  float arg1
  float arg2
</pre>
</pre>




The actual rotation code:
The actual rotation code:
<pre>
<pre>
;sine and cosine of local x, y, z angles
  set axisOrder to 3
</pre>
 
<pre>
   set sx to sin localAngX
   set sx to sin localAngX
   set cx to cos localAngX
   set cx to cos localAngX
Line 48: Line 73:




;extraction of world x angle
;creation of rotation matrix in axis order
   set arg1 to -( cx * -sy *  cz +  sx *  sz)
   if    (axisOrder == 1) 
  set arg2 to       ( cy *  cz)
    ;XYZ
  set worldAngX to atan2 arg1 arg2
    set r11 to ( cy *  cz)
    set r12 to ( cy * -sz)
    set r13 to (    sy  )
    set r21 to ( sx *  sy *  cz +  cx *  sz)
    set r22 to ( sx *  sy * -sz +  cx *  cz)
    set r23 to (-sx *  cy)
    set r31 to ( cx * -sy *  cz +  sx *  sz)
    set r32 to ( cx *  sy *  sz +  sx *  cz)
    set r33 to ( cx *  cy)


  elseif (axisOrder == 2)
    ;XZY
    set r11 to ( cz *  cy)
    set r12 to (  -sz  )
    set r13 to ( cz *  sy)
    set r21 to ( cx *  sz *  cy +  cx *  cz)
    set r22 to ( cx *  cz)
    set r23 to ( cx *  sz *  sy + -sx *  cy)
    set r31 to ( sx *  sz *  cy +  cx * -sy)
    set r32 to ( sx *  cz)
    set r33 to ( sx *  sz *  sy +  cx *  cy)


;extraction of world y angle
  elseif (axisOrder == 3)
  set arg1 to -(-sx * -sy *  cz +  cx *  sz)
    ;YXZ
  set worldAngY to asin arg1
    set r11 to ( cy *  cz +  sy *  sx *  sz)
    set r12 to ( cy * -sz +  sy *  sx *  cz)
    set r13 to ( sy *  cx)
    set r21 to ( cx *  sz)
    set r22 to ( cx *  cz)
    set r23 to (  -sx   )
    set r31 to (-sy *  cz +  cy *  sx *  sz)
    set r32 to ( sy *  sz +  cy *  sx *  cz)
    set r33 to ( cy *  cx)


  elseif (axisOrder == 4)
    ;YZX
    set r11 to ( cy *  cz)
    set r12 to ( cy * -sz *  cx +  sy *  sx)
    set r13 to ( cy * -sz * -sx +  sy *  cx)
    set r21 to (    sz  )
    set r22 to ( cz *  cx)
    set r23 to ( cz * -sx)
    set r31 to (-sy *  cz)
    set r32 to ( sy *  sz *  cx +  cy *  sx)
    set r33 to ( sy *  sz * -sx +  cy *  cx)


;extraction of world z angle
  elseif (axisOrder == 5)
  set arg1 to       -(-sx *  cy)
    ;ZXY
  set arg2 to  (-sx * -sy * -sz +  cx *  cz)
    set r11 to ( cz *  cy +  sz *  sx * -sy)
   set worldAngZ to atan2 arg1 arg2
    set r12 to (-sz *  cx)
    set r13 to ( cz *  sy +  sz *  sx *  cy)
    set r21 to ( sz *  cy +  cz *  sx *  sy)
    set r22 to ( cz * cx)
    set r23 to ( sz *  sy +  cz * -sx *  cy)
    set r31 to ( cx * -sy)
    set r32 to (  sx    )
    set r33 to ( cx *  cy)
 
  else
    ;ZYX
    set r11 to ( cz * cy)
    set r12 to (-sz *  cx +  cz *  sy *  sx)
    set r13 to ( sz *  sx + cz *  sy * cx)
    set r21 to ( sz cy)
    set r22 to ( cz *  cx +  sz *  sy *  sx)
    set r23 to ( cz * -sx +  sz *  sy *  cx)
    set r31 to (   -sy  )
    set r32 to ( cy *  sx)
    set r33 to ( cy *  cx)
 
  endif
 
 
;Extraction of worldangles from rotation matrix
  if    (r13 >  0.9998)
    ;positive gimbal lock
 
    set worldAngX to -ATan2 r32 r22
    set worldAngY to -90
    set worldAngZ to 0
 
  elseif (r13 < -0.9998)
    ;negative gimbal lock
 
    set worldAngX to -ATan2 r32 r22
    set worldAngY to 90
    set worldAngZ to 0
 
  else
    ;no gimbal lock
 
    set r23 to -r23
    set r12 to -r12
    set worldAngX to -ATan2 r23 r33
    set worldAngY to -ASin r13
    set worldAngZ to -ATan2 r12 r11
 
  endif




Line 69: Line 180:
   myObject.setAngle y worldAngY
   myObject.setAngle y worldAngY
   myObject.setAngle z worldAngZ
   myObject.setAngle z worldAngZ
</pre>
</pre>




==Special Note==
==Special Note==
This code has been tested ingame and works reliably well. It is advised to experiment with the order of the local angles to get the desired result. <br />
This code has been tested ingame and works reliably well. It is advided to pay special attention to the axis order.  
E.g. this...
 
<pre>
set localAngX to 0
set localAngY to player.getangle y
set localAngZ to player.getangle z
</pre>
...will result in a different rotation than this:
<pre>
set localAngX to player.getangle z
set localAngY to player.getangle x
set localAngZ to 0
</pre>


==Notes==
==Notes==
*Some types of object, specifically Actors, do not allow for rotation about all three axes. Other objects, such as activators have all three degrees of freedom.   
*Some types of object, specifically Actors, do not allow for rotation about all three axes. Other objects, such as activators have all three degrees of freedom.   
*The equations were determined via matrix multiplication of standard rotation matrices in X*Y*Z axis order. Unused matrix elements are omitted in the code.
*[http://en.wikipedia.org/wiki/Gimbal_lock Gimbal Lock] is accounted for. Whenever two of the axes are orthogonal, becoming arbitrary, two of the axes (y and z) are reset to a more preferable orientation.  
*[http://en.wikipedia.org/wiki/Gimbal_lock Gimbal Lock] will occur when '''(-sx * -sy *  cz +  cx *  sz)''' equals '''1''' or '''-1'''. No reliable solution is implemented yet.  
*It is not certain to the author whether Oblivion actually uses an axis order of XYZ, but it is assumed. Falsification / Verification of this claim is hereby requested.  
*For a more in-depth discussion of the math behind this, see [http://en.wikipedia.org/wiki/Rotation_representation_%28mathematics%29 wikipedia.org] and [http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm euclideanspace.com].  
*For a more in-depth discussion of the math behind this, see [http://en.wikipedia.org/wiki/Rotation_representation_%28mathematics%29 wikipedia.org] and [http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm euclideanspace.com].  



Revision as of 20:09, 4 June 2011

The functions SetAngle and Rotate rotate objects about the world axes. In order to apply local rotations, the angles need to be derived first. It is possible to rotate an object about local angles by first specifying the local angles, then extracting the world angles from the local rotation and applying those to the object.

Any local rotation is dependent on the order in which the individual rotations are applied. This is true because the local axes are dependent on each other.

Because of this, it is important to specify the axis order prior to calculating the world rotation.

An axis order of XYZ means that the X rotation is applied first, then the Y and lastly the Z rotation.


These declarations are needed:

;the object one wishes to rotate
  ref myObject

;specifes order of rotations.
  float axisOrder
  ;1 = xyz
  ;2 = xzy
  ;3 = yxz
  ;4 = yzx
  ;5 = zxy
  ;6 = zyx


;the local angles
  float localAngX
  float localAngY
  float localAngZ


;the world angles
  float worldAngX
  float worldAngY
  float worldAngZ


;sine and cosine of local x, y, z angles
  float sx
  float cx
  float sy
  float cy
  float sz
  float cz

;the cells of the rotation matrix
  float r11
  float r12
  float r13
  float r21
  float r22
  float r23
  float r31
  float r32
  float r33


The actual rotation code:

  set axisOrder to 3
  set sx to sin localAngX
  set cx to cos localAngX
  set sy to sin localAngY
  set cy to cos localAngY
  set sz to sin localAngZ
  set cz to cos localAngZ


;creation of rotation matrix in axis order
  if     (axisOrder == 1)  
    ;XYZ
    set r11 to ( cy *  cz)
    set r12 to ( cy * -sz)
    set r13 to (    sy   )
    set r21 to ( sx *  sy *  cz +  cx *  sz)
    set r22 to ( sx *  sy * -sz +  cx *  cz)
    set r23 to (-sx *  cy)
    set r31 to ( cx * -sy *  cz +  sx *  sz)
    set r32 to ( cx *  sy *  sz +  sx *  cz)
    set r33 to ( cx *  cy)

  elseif (axisOrder == 2)
    ;XZY
    set r11 to ( cz *  cy)
    set r12 to (   -sz   )
    set r13 to ( cz *  sy)
    set r21 to ( cx *  sz *  cy +  cx *  cz)
    set r22 to ( cx *  cz)
    set r23 to ( cx *  sz *  sy + -sx *  cy)
    set r31 to ( sx *  sz *  cy +  cx * -sy)
    set r32 to ( sx *  cz)
    set r33 to ( sx *  sz *  sy +  cx *  cy)

  elseif (axisOrder == 3)
    ;YXZ
    set r11 to ( cy *  cz +  sy *  sx *  sz)
    set r12 to ( cy * -sz +  sy *  sx *  cz)
    set r13 to ( sy *  cx)
    set r21 to ( cx *  sz)
    set r22 to ( cx *  cz)
    set r23 to (   -sx   )
    set r31 to (-sy *  cz +  cy *  sx *  sz)
    set r32 to ( sy *  sz +  cy *  sx *  cz)
    set r33 to ( cy *  cx)

  elseif (axisOrder == 4)
    ;YZX
    set r11 to ( cy *  cz)
    set r12 to ( cy * -sz *  cx +  sy *  sx)
    set r13 to ( cy * -sz * -sx +  sy *  cx)
    set r21 to (    sz   )
    set r22 to ( cz *  cx)
    set r23 to ( cz * -sx)
    set r31 to (-sy *  cz)
    set r32 to ( sy *  sz *  cx +  cy *  sx)
    set r33 to ( sy *  sz * -sx +  cy *  cx)

  elseif (axisOrder == 5)
    ;ZXY
    set r11 to ( cz *  cy +  sz *  sx * -sy)
    set r12 to (-sz *  cx)
    set r13 to ( cz *  sy +  sz *  sx *  cy)
    set r21 to ( sz *  cy +  cz *  sx *  sy)
    set r22 to ( cz *  cx)
    set r23 to ( sz *  sy +  cz * -sx *  cy)
    set r31 to ( cx * -sy)
    set r32 to (   sx    )
    set r33 to ( cx *  cy)
  
  else
    ;ZYX
    set r11 to ( cz *  cy)
    set r12 to (-sz *  cx +  cz *  sy *  sx)
    set r13 to ( sz *  sx +  cz *  sy *  cx)
    set r21 to ( sz *  cy)
    set r22 to ( cz *  cx +  sz *  sy *  sx)
    set r23 to ( cz * -sx +  sz *  sy *  cx)
    set r31 to (   -sy   )
    set r32 to ( cy *  sx)
    set r33 to ( cy *  cx)
  
  endif


;Extraction of worldangles from rotation matrix
  if     (r13 >  0.9998)
    ;positive gimbal lock

    set worldAngX to -ATan2 r32 r22
    set worldAngY to -90
    set worldAngZ to 0

  elseif (r13 < -0.9998)
    ;negative gimbal lock

    set worldAngX to -ATan2 r32 r22
    set worldAngY to 90
    set worldAngZ to 0

  else
    ;no gimbal lock

    set r23 to -r23
    set r12 to -r12
    set worldAngX to -ATan2 r23 r33
    set worldAngY to -ASin r13
    set worldAngZ to -ATan2 r12 r11

  endif


;apply extracted rotation
  myObject.setAngle x worldAngX
  myObject.setAngle y worldAngY
  myObject.setAngle z worldAngZ


Special Note

This code has been tested ingame and works reliably well. It is advided to pay special attention to the axis order.


Notes

  • Some types of object, specifically Actors, do not allow for rotation about all three axes. Other objects, such as activators have all three degrees of freedom.
  • Gimbal Lock is accounted for. Whenever two of the axes are orthogonal, becoming arbitrary, two of the axes (y and z) are reset to a more preferable orientation.
  • For a more in-depth discussion of the math behind this, see wikipedia.org and euclideanspace.com.


See Also