-------
## 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):
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.*
-------
## 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 )
)
);
}
```
-------
-------
-------