ROTORMATH.XYZ

------- ## STRUCTURE::ROTOR A rotor efficiently represents a rotation (from forward) in 3D space, and sits of the surface of a hypersphere. It uses 2 components: a [vector](?page=vector) and a [hangle](?page=hangle):
h v
Compared to a matrix which rotates via 3 individual planes in a sequence, a rotor merges the whole rotation into a single structure. #### COMPONENTS: ```glsl struct rotor { vector v; hangle h; }; ``` #### ALIGNMENTS: - World-Space Orientation: - +X Forward, +Y Left, +Z Up - Intrinsic: **Y**aw, then **P**itch, then **R**oll (ZXY) - While looking [forward](?page=up): - **+Yaw**: turns left - **+Pitch**: tilts down - **+Roll**: spins clockwise *A positive rotation around any axis is always **counterclockwise** when viewing from the positive-axis towards the rotating plane.*
+ yaw: XY plane + pitch: ZX plane + roll: YZ plane
------- ## ROTOR::SHADERTOY Click and drag the mouse around to rotate the cube via a rotor:
------- ## ROTOR::NEW The most minimal way to make a new rotor is with a normalized vector, and a hangle. See what it's doing; it's turning the vector itself into a rotating object via sine and cosine: ```glsl rotor new_rotor( in vector norm_v, in hangle h ) { return rotor( norm_v * sin( h ), cos( h ) ); } ``` or if you only need to rotate on a specific axis, breaking it up into functions is slightly more efficient: ```glsl rotor yaw_rotor( in hangle h ) // Z AXIS { return rotor( vector( 0., 0., sin( h ) ), cos( h ) ); } rotor pitch_rotor( in hangle h ) // Y AXIS { return rotor( vector( 0., sin( h ), 0. ), cos( h ) ); } rotor roll_rotor( in hangle h ) // X AXIS { return rotor( vector( sin( h ), 0., 0. ), cos( h ) ); } ``` Which map to the 3 planes shown above. ------- ## ROTOR::INVERT Inverts the rotational direction of the rotor (used for [observers](?page=observer)): ```glsl rotor invert_rotor( in rotor r ) { return rotor( invert_vector( r.v ), r.h ); } ``` ------- ## ROTOR::LOOK A minimal way to make a rotor that points in the direction from one point to another. This aligns with what is stated in [CONCEPT::FORWARD](?page=orientation), where the result of `atan` (or `atan2` in other languages) gives `0` when looking Forward with +X: ```glsl rotor look_rotor( in vector from_v, in vector to_v, in hangle roll ) { vector dir = vector_normalize( to_v - from_v ); return yaw_pitch_roll( atan( dir.y, dir.x ) * .5, asin( -dir.z ) * .5, roll ); } ``` ------- ## ROTOR::MULTIPLY To rotate one rotor by another, you multiply them. But since a rotor is a structure, they must be multiplied in a certain way. Every time you multiply a rotor with another, the rotation happens via the first rotor's orientation. Rotors are Non-Commutative! ### a × b ≠ b × a ```glsl rotor rotor_mul( in rotor a, in rotor b ) { return rotor( a.h * b.v + b.h * a.v + vector_cross( a.v, b.v ), a.h * b.h - vector_dot( a.v, b.v ) ); } ``` *expanded and optimized:* ```glsl rotor rotor_mul( in rotor a, in rotor b ) { float i = ( a.v.z + a.v.x ) * ( b.v.x + b.v.y ); float j = ( a.h + a.v.y ) * ( b.h - b.v.z ); float k = ( a.h - a.v.y ) * ( b.h + b.v.z ); float l = i + j + k; float m = .5 * ( ( a.v.z - a.v.x ) * ( b.v.x - b.v.y ) + l ); return rotor( vector( ( a.h + a.v.x ) * ( b.h + b.v.x ) + m - l, ( a.h - a.v.x ) * ( b.v.y + b.v.z ) + m - k, ( a.v.y + a.v.z ) * ( b.h - b.v.x ) + m - j ), ( a.v.z - a.v.y ) * ( b.v.y - b.v.z ) + m - i ); } ``` ## ROTOR::ADD Addition is simple, but is really only used in [motor](?page=motor) code. To rotate a rotor by another, look above to `ROTOR::MULTIPLY`. ```glsl rotor rotor_add( in rotor a, in rotor b ) { return rotor( a.v + b.v, a.h + b.h ); } ``` ------- ## ROTOR::CONSTRUCT Combining the multiply and rotor-making functions You can create a more practical YPR constructor. The multiplication order is Yaw, Pitch, then Roll - an "intrinsic" rotation. Intrinsic means "inherent," so the rotation is applied to the rotor during multiplication - from its "point of view". It's intuitive from the rotated object's perspective. We start with the Yaw rotor, apply Pitch, then Roll: ```glsl rotor yaw_pitch_roll( in hangle yaw, in hangle pitch, in hangle roll ) { return rotor_mul( rotor_mul( yaw_rotor( yaw ), pitch_rotor( pitch ) ), roll_rotor( roll ) ); } ``` *expanded and optimized:* ```glsl rotor yaw_pitch_roll( in hangle yaw, in hangle pitch, in hangle roll ) { float sy = sin( yaw ); float cy = cos( yaw ); float sp = sin( pitch ); float cp = cos( pitch ); float sr = sin( roll ); float cr = cos( roll ); return rotor( vector( sr * cp * cy - cr * sp * sy, cr * sp * cy + sr * cp * sy, cr * cp * sy - sr * sp * cy ), cr * cp * cy + sr * sp * sy ); } ``` ------- ## VECTOR::ROTATE Finally, to rotate a point in 3D space around the origin by a rotor you can use this method: ```glsl vector vector_rotate( in vector v, in rotor r ) { vector c = 2. * vector_cross( r.v, v ); return v + r.h * c - vector_cross( c, r.v ); } ``` *expanded and optimized:* ```glsl vector vector_rotate( in vector v, in rotor r ) { float a = r.v.y * v.z - r.v.z * v.y; float b = r.v.z * v.x - r.v.x * v.z; float c = r.v.x * v.y - r.v.y * v.x; return vector( v.x + 2. * ( r.h * a + r.v.y * c - r.v.z * b ), v.y + 2. * ( r.h * b + r.v.z * a - r.v.x * c ), v.z + 2. * ( r.h * c + r.v.x * b - r.v.y * a ) ); } ``` ------- ## ROTOR::NORMALIZE Rotors are unit-objects, so they need to stay a constant unit size. If you're constantly making it, then it's fine. But if you have a persistent rotor that's being multiplied to and changed, then it's good to normalize it at least once before using. This is just due to how rotors change when operated on. We first need a function to get the magnitude: ```glsl float rotor_magnitude( in rotor a, in rotor b ) { return vector_dot( a.v, b.v ) + ( a.h * b.h ); } ``` Which allows us to normalize the rotor: ```glsl rotor rotor_normalize( in rotor r ) { float mag = rotor_magnitude( r, r ); if( mag < 1e-7 ) // almost zero { return rotor( vector(0.), 1. ); } float inv_mag = 1. / sqrt( mag ); return rotor( r.v * inv_mag, r.h * inv_mag ); } ``` ------- ## ROTOR::MIX You can very easily mix two rotors together via any lerp/mix function, but to rotate with the shorter distance we first have to check if the combined magnitude is negative; if so then invert the vector and hangle of the other rotor: ```glsl rotor rotor_mix( in rotor a, in rotor b, in float t ) { float mag = rotor_magnitude( a, b ); if( mag < 0. ) { b.v = invert_vector( b.v ); b.h = -b.h; } return rotor_normalize( rotor( mix( a.v, b.v, t ), mix( a.h, b.h, t ) ) ); } ``` ------- ------- -------
previous
next
← vector
home
motor →