Skip to content

Euler Axis (Axis-Angle)

The Euler axis representation describes rotations using a rotation axis and angle.

Overview

Also known as axis-angle representation, this describes any rotation as a single rotation about a unit vector (axis) by a specified angle.

Mathematical Representation

An Euler axis rotation is specified by:

  • Unit vector (axis): \(\hat{n} = [n_x, n_y, n_z]\) where \(|\hat{n}| = 1\)
  • Rotation angle: \(\theta\) (in radians)

Together: \([\theta, n_x, n_y, n_z]\) (4 parameters)

Initialization

Euler axis representations can be created from an axis vector and angle, or converted from other attitude representations:

import brahe as bh
import numpy as np
import math

# Initialize from axis vector and angle
# 45° rotation about Z-axis
axis_z = np.array([0.0, 0.0, 1.0])
angle = math.radians(45.0)
ea_z = bh.EulerAxis(axis_z, angle, bh.AngleFormat.RADIANS)

print("45° rotation about Z-axis:")
print(f"  Axis: [{ea_z.axis[0]:.3f}, {ea_z.axis[1]:.3f}, {ea_z.axis[2]:.3f}]")
print(f"  Angle: {math.degrees(ea_z.angle):.1f}°")

# 90° rotation about X-axis
axis_x = np.array([1.0, 0.0, 0.0])
ea_x = bh.EulerAxis(axis_x, math.radians(90.0), bh.AngleFormat.RADIANS)

print("\n90° rotation about X-axis:")
print(f"  Axis: [{ea_x.axis[0]:.3f}, {ea_x.axis[1]:.3f}, {ea_x.axis[2]:.3f}]")
print(f"  Angle: {math.degrees(ea_x.angle):.1f}°")

# Initialize from another representation (quaternion)
q = bh.Quaternion(math.cos(math.pi / 8), 0.0, 0.0, math.sin(math.pi / 8))
ea_from_q = bh.EulerAxis.from_quaternion(q)

print("\nFrom quaternion (45° about Z):")
print(
    f"  Axis: [{ea_from_q.axis[0]:.6f}, {ea_from_q.axis[1]:.6f}, {ea_from_q.axis[2]:.6f}]"
)
print(f"  Angle: {math.degrees(ea_from_q.angle):.1f}°")

# Initialize from rotation matrix
rm = bh.RotationMatrix.Rz(45, bh.AngleFormat.DEGREES)
ea_from_rm = bh.EulerAxis.from_rotation_matrix(rm)

print("\nFrom rotation matrix (45° about Z):")
print(
    f"  Axis: [{ea_from_rm.axis[0]:.6f}, {ea_from_rm.axis[1]:.6f}, {ea_from_rm.axis[2]:.6f}]"
)
print(f"  Angle: {math.degrees(ea_from_rm.angle):.1f}°")

# Initialize from EulerAngle
euler_angle = bh.EulerAngle(
    bh.EulerAngleOrder.ZYX, 45.0, 0.0, 0.0, bh.AngleFormat.DEGREES
)
ea_from_euler = bh.EulerAxis.from_euler_angle(euler_angle)

print("\nFrom EulerAngle (45° about Z):")
print(
    f"  Axis: [{ea_from_euler.axis[0]:.6f}, {ea_from_euler.axis[1]:.6f}, {ea_from_euler.axis[2]:.6f}]"
)
print(f"  Angle: {math.degrees(ea_from_euler.angle):.1f}°")

# Expected output:
# 45° rotation about Z-axis:
#   Axis: [0.000, 0.000, 1.000]
#   Angle: 45.0°

# 90° rotation about X-axis:
#   Axis: [1.000, 0.000, 0.000]
#   Angle: 90.0°

# From quaternion (45° about Z):
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.0°

# From rotation matrix (45° about Z):
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.0°

# From EulerAngle (45° about Z):
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.0°
use brahe as bh;
use brahe::attitude::FromAttitude;
use nalgebra as na;
use std::f64::consts::PI;

fn main() {
    // Initialize from axis vector and angle
    // 45° rotation about Z-axis
    let axis_z = na::SVector::<f64, 3>::new(0.0, 0.0, 1.0);
    let angle = (45.0_f64).to_radians();
    let ea_z = bh::EulerAxis::new(axis_z, angle, bh::AngleFormat::Radians);

    println!("45° rotation about Z-axis:");
    println!("  Axis: [{:.3}, {:.3}, {:.3}]", ea_z.axis[0], ea_z.axis[1], ea_z.axis[2]);
    println!("  Angle: {:.1}°", ea_z.angle.to_degrees());

    // 90° rotation about X-axis
    let axis_x = na::SVector::<f64, 3>::new(1.0, 0.0, 0.0);
    let ea_x = bh::EulerAxis::new(axis_x, (90.0_f64).to_radians(), bh::AngleFormat::Radians);

    println!("\n90° rotation about X-axis:");
    println!("  Axis: [{:.3}, {:.3}, {:.3}]", ea_x.axis[0], ea_x.axis[1], ea_x.axis[2]);
    println!("  Angle: {:.1}°", ea_x.angle.to_degrees());

    // Initialize from another representation (quaternion)
    let q = bh::Quaternion::new((PI/8.0).cos(), 0.0, 0.0, (PI/8.0).sin());
    let ea_from_q = bh::EulerAxis::from_quaternion(q);

    println!("\nFrom quaternion (45° about Z):");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]",
             ea_from_q.axis[0], ea_from_q.axis[1], ea_from_q.axis[2]);
    println!("  Angle: {:.1}°", ea_from_q.angle.to_degrees());

    // Initialize from rotation matrix
    let rm = bh::RotationMatrix::Rz(45.0, bh::AngleFormat::Degrees);
    let ea_from_rm = bh::EulerAxis::from_rotation_matrix(rm);

    println!("\nFrom rotation matrix (45° about Z):");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]",
             ea_from_rm.axis[0], ea_from_rm.axis[1], ea_from_rm.axis[2]);
    println!("  Angle: {:.1}°", ea_from_rm.angle.to_degrees());

    // Initialize from EulerAngle
    let euler_angle = bh::EulerAngle::new(
        bh::EulerAngleOrder::ZYX,
        (45.0_f64).to_radians(),
        0.0,
        0.0,
        bh::AngleFormat::Radians
    );
    let ea_from_euler = bh::EulerAxis::from_euler_angle(euler_angle);

    println!("\nFrom EulerAngle (45° about Z):");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]",
             ea_from_euler.axis[0], ea_from_euler.axis[1], ea_from_euler.axis[2]);
    println!("  Angle: {:.1}°", ea_from_euler.angle.to_degrees());
}

// Expected output:
// 45° rotation about Z-axis:
//   Axis: [0.000, 0.000, 1.000]
//   Angle: 45.0°
//
// 90° rotation about X-axis:
//   Axis: [1.000, 0.000, 0.000]
//   Angle: 90.0°
//
// From quaternion (45° about Z):
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.0°
//
// From rotation matrix (45° about Z):
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.0°
//
// From EulerAngle (45° about Z):
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.0°

Conversions

Convert between Euler axis and other attitude representations:

import brahe as bh
import numpy as np
import math

# Create an Euler axis (45° rotation about Z-axis)
ea = bh.EulerAxis(np.array([0.0, 0.0, 1.0]), math.radians(45.0), bh.AngleFormat.RADIANS)

print("Original Euler axis:")
print(f"  Axis: [{ea.axis[0]:.6f}, {ea.axis[1]:.6f}, {ea.axis[2]:.6f}]")
print(f"  Angle: {math.degrees(ea.angle):.1f}°")

# Convert to quaternion
q = ea.to_quaternion()
print("\nTo quaternion:")
print(f"  q = [{q.w:.6f}, {q.x:.6f}, {q.y:.6f}, {q.z:.6f}]")

# Convert to rotation matrix
rm = ea.to_rotation_matrix()
print("\nTo rotation matrix:")
print(f"  [{rm.r11:.6f}, {rm.r12:.6f}, {rm.r13:.6f}]")
print(f"  [{rm.r21:.6f}, {rm.r22:.6f}, {rm.r23:.6f}]")
print(f"  [{rm.r31:.6f}, {rm.r32:.6f}, {rm.r33:.6f}]")

# Convert to Euler angles (ZYX sequence)
ea_angles_zyx = ea.to_euler_angle(bh.EulerAngleOrder.ZYX)
print("\nTo Euler angles (ZYX):")
print(f"  Yaw (Z):   {math.degrees(ea_angles_zyx.phi):.3f}°")
print(f"  Pitch (Y): {math.degrees(ea_angles_zyx.theta):.3f}°")
print(f"  Roll (X):  {math.degrees(ea_angles_zyx.psi):.3f}°")

# Convert to Euler angles (XYZ sequence)
ea_angles_xyz = ea.to_euler_angle(bh.EulerAngleOrder.XYZ)
print("\nTo Euler angles (XYZ):")
print(f"  Angle 1 (X): {math.degrees(ea_angles_xyz.phi):.3f}°")
print(f"  Angle 2 (Y): {math.degrees(ea_angles_xyz.theta):.3f}°")
print(f"  Angle 3 (Z): {math.degrees(ea_angles_xyz.psi):.3f}°")

# Round-trip conversion test
q_roundtrip = ea.to_quaternion()
ea_roundtrip = bh.EulerAxis.from_quaternion(q_roundtrip)
print("\nRound-trip (EulerAxis → Quaternion → EulerAxis):")
print(
    f"  Axis: [{ea_roundtrip.axis[0]:.6f}, {ea_roundtrip.axis[1]:.6f}, {ea_roundtrip.axis[2]:.6f}]"
)
print(f"  Angle: {math.degrees(ea_roundtrip.angle):.1f}°")

# Expected output:
# Original Euler axis:
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.0°

# To quaternion:
#   q = [0.923880, 0.000000, 0.000000, 0.382683]

# To rotation matrix:
#   [0.707107, 0.707107, 0.000000]
#   [-0.707107, 0.707107, 0.000000]
#   [0.000000, 0.000000, 1.000000]

# To Euler angles (ZYX):
#   Yaw (Z):   45.000°
#   Pitch (Y): 0.000°
#   Roll (X):  -0.000°

# To Euler angles (XYZ):
#   Angle 1 (X): 0.000°
#   Angle 2 (Y): -0.000°
#   Angle 3 (Z): 45.000°

# Round-trip (EulerAxis → Quaternion → EulerAxis):
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.0°
use brahe as bh;
use brahe::attitude::FromAttitude;
use brahe::attitude::ToAttitude;
use nalgebra as na;

fn main() {
    // Create an Euler axis (45° rotation about Z-axis)
    let ea = bh::EulerAxis::new(
        na::SVector::<f64, 3>::new(0.0, 0.0, 1.0),
        (45.0_f64).to_radians(),
        bh::AngleFormat::Radians
    );

    println!("Original Euler axis:");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]", ea.axis[0], ea.axis[1], ea.axis[2]);
    println!("  Angle: {:.1}°", ea.angle.to_degrees());

    // Convert to quaternion
    let q = ea.to_quaternion();
    println!("\nTo quaternion:");
    println!("  q = [{:.6}, {:.6}, {:.6}, {:.6}]", q[0], q[1], q[2], q[3]);

    // Convert to rotation matrix
    let rm = ea.to_rotation_matrix();
    println!("\nTo rotation matrix:");
    println!("  [{:.6}, {:.6}, {:.6}]", rm[(0, 0)], rm[(0, 1)], rm[(0, 2)]);
    println!("  [{:.6}, {:.6}, {:.6}]", rm[(1, 0)], rm[(1, 1)], rm[(1, 2)]);
    println!("  [{:.6}, {:.6}, {:.6}]", rm[(2, 0)], rm[(2, 1)], rm[(2, 2)]);

    // Convert to Euler angles (ZYX sequence)
    let ea_angles_zyx = ea.to_euler_angle(bh::EulerAngleOrder::ZYX);
    println!("\nTo Euler angles (ZYX):");
    println!("  Yaw (Z):   {:.3}°", ea_angles_zyx.phi.to_degrees());
    println!("  Pitch (Y): {:.3}°", ea_angles_zyx.theta.to_degrees());
    println!("  Roll (X):  {:.3}°", ea_angles_zyx.psi.to_degrees());

    // Convert to Euler angles (XYZ sequence)
    let ea_angles_xyz = ea.to_euler_angle(bh::EulerAngleOrder::XYZ);
    println!("\nTo Euler angles (XYZ):");
    println!("  Angle 1 (X): {:.3}°", ea_angles_xyz.phi.to_degrees());
    println!("  Angle 2 (Y): {:.3}°", ea_angles_xyz.theta.to_degrees());
    println!("  Angle 3 (Z): {:.3}°", ea_angles_xyz.psi.to_degrees());

    // Round-trip conversion test
    let q_roundtrip = ea.to_quaternion();
    let ea_roundtrip = bh::EulerAxis::from_quaternion(q_roundtrip);
    println!("\nRound-trip (EulerAxis → Quaternion → EulerAxis):");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]",
             ea_roundtrip.axis[0], ea_roundtrip.axis[1], ea_roundtrip.axis[2]);
    println!("  Angle: {:.1}°", ea_roundtrip.angle.to_degrees());
}

// Expected output:
// Original Euler axis:
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.0°

// To quaternion:
//   q = [0.923880, 0.000000, 0.000000, 0.382683]

// To rotation matrix:
//   [0.707107, 0.707107, 0.000000]
//   [-0.707107, 0.707107, 0.000000]
//   [0.000000, 0.000000, 1.000000]

// To Euler angles (ZYX):
//   Yaw (Z):   45.000°
//   Pitch (Y): 0.000°
//   Roll (X):  -0.000°

// To Euler angles (XYZ):
//   Angle 1 (X): 0.000°
//   Angle 2 (Y): -0.000°
//   Angle 3 (Z): 45.000°

// Round-trip (EulerAxis → Quaternion → EulerAxis):
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.0°

See Also