Skip to content

Euler Angles

Euler angles represent rotations as three sequential rotations about coordinate axes.

Overview

Euler angles describe orientation using three angles representing sequential rotations about specified axes. Brahe supports all 12 possible Euler angle sequences (e.g., XYZ, ZYX, ZYZ).

Mathematical Representation

An Euler angle rotation is specified by:

  • Three angles: \((\phi, \theta, \psi)\)
  • A rotation sequence (e.g., XYZ, ZYX)

Sequences

Brahe supports all valid Euler angle sequences, though some are more commonly used are:

  • ZYX (3-2-1): Common in aerospace applications. Known as yaw-pitch-roll.
  • XYZ (1-2-3): Common in robotics
  • ZYZ (3-1-3): Common in classical mechanics

Initialization

Euler angles can be created from individual angles with a specified rotation sequence, or converted from other attitude representations. When creating a new EulerAngle object, the rotation sequence of the created object must be specified.

import brahe as bh
import numpy as np
import math

# Initialize from individual angles with ZYX sequence (yaw-pitch-roll)
# 45° yaw, 30° pitch, 15° roll
ea_zyx = bh.EulerAngle(
    bh.EulerAngleOrder.ZYX,
    45.0,  # Yaw (Z)
    30.0,  # Pitch (Y)
    15.0,  # Roll (X)
    bh.AngleFormat.DEGREES,
)
print("ZYX Euler angles (yaw-pitch-roll):")
print(f"  Yaw (Z):   {math.degrees(ea_zyx.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea_zyx.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea_zyx.psi):.1f}°")
print(f"  Order: {ea_zyx.order}")

# Initialize from vector with XYZ sequence
angles_vec = np.array([15.0, 30.0, 45.0])
ea_xyz = bh.EulerAngle.from_vector(
    angles_vec, bh.EulerAngleOrder.XYZ, bh.AngleFormat.DEGREES
)
print("\nXYZ Euler angles (from vector):")
print(f"  Angle 1 (X): {math.degrees(ea_xyz.phi):.1f}°")
print(f"  Angle 2 (Y): {math.degrees(ea_xyz.theta):.1f}°")
print(f"  Angle 3 (Z): {math.degrees(ea_xyz.psi):.1f}°")
print(f"  Order: {ea_xyz.order}")

# Simple rotation about single axis (45° about Z using ZYX)
ea_z_only = bh.EulerAngle(
    bh.EulerAngleOrder.ZYX,
    45.0,  # Z
    0.0,  # Y
    0.0,  # X
    bh.AngleFormat.DEGREES,
)
print("\nSingle-axis rotation (45° about Z using ZYX):")
print(f"  Yaw (Z):   {math.degrees(ea_z_only.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea_z_only.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea_z_only.psi):.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.EulerAngle.from_quaternion(q, bh.EulerAngleOrder.ZYX)
print("\nFrom quaternion (45° about Z):")
print(f"  Yaw (Z):   {math.degrees(ea_from_q.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea_from_q.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea_from_q.psi):.1f}°")

# Initialize from Rotation Matrix
rm = bh.RotationMatrix.Rz(45.0, bh.AngleFormat.DEGREES)
ea_from_rm = bh.EulerAngle.from_rotation_matrix(rm, bh.EulerAngleOrder.ZYX)
print("\nFrom rotation matrix (45° about Z):")
print(f"  Yaw (Z):   {math.degrees(ea_from_rm.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea_from_rm.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea_from_rm.psi):.1f}°")

# Initialize from Euler Axis
euler_axis = bh.EulerAxis(np.array([0.0, 0.0, 1.0]), 45.0, bh.AngleFormat.DEGREES)
ea_from_ea = bh.EulerAngle.from_euler_axis(euler_axis, bh.EulerAngleOrder.ZYX)

print("\nFrom Euler axis (45° about Z):")
print(f"  Yaw (Z):   {math.degrees(ea_from_ea.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea_from_ea.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea_from_ea.psi):.1f}°")

# Initialize from one EulerAngle to another with different order
# Start with XZY order
ea_xzy = bh.EulerAngle.from_euler_angle(ea_zyx, bh.EulerAngleOrder.XZY)
print("\nXZY Euler angles from ZYX:")
print(f"  Angle 1 (X): {math.degrees(ea_xzy.phi):.1f}°")
print(f"  Angle 2 (Z): {math.degrees(ea_xzy.theta):.1f}°")
print(f"  Angle 3 (Y): {math.degrees(ea_xzy.psi):.1f}°")
print(f"  Order: {ea_xzy.order}")

# Convert to ZYX order (same physical rotation, different representation)
# Go through quaternion as intermediate representation
q_xzy = ea_xzy.to_quaternion()
ea_zyx_converted = bh.EulerAngle.from_quaternion(q_xzy, bh.EulerAngleOrder.ZYX)
print("\nConverted back to ZYX order (same rotation):")
print(f"  Angle 1 (Z): {math.degrees(ea_zyx_converted.phi):.1f}°")
print(f"  Angle 2 (Y): {math.degrees(ea_zyx_converted.theta):.1f}°")
print(f"  Angle 3 (X): {math.degrees(ea_zyx_converted.psi):.1f}°")
print(f"  Order: {ea_zyx_converted.order}")

# Expected output:
# ZYX Euler angles (yaw-pitch-roll):
#   Yaw (Z):   45.0°
#   Pitch (Y): 30.0°
#   Roll (X):  15.0°
#   Order: ZYX
#
# XYZ Euler angles (from vector):
#   Angle 1 (X): 15.0°
#   Angle 2 (Y): 30.0°
#   Angle 3 (Z): 45.0°
#   Order: XYZ
#
# Single-axis rotation (45° about Z using ZYX):
#   Yaw (Z):   45.0°
#   Pitch (Y): 0.0°
#   Roll (X):  0.0°
#
# From quaternion (45° about Z):
#   Yaw (Z):   45.0°
#   Pitch (Y): 0.0°
#   Roll (X):  0.0°
#
# From rotation matrix (45° about Z):
#   Yaw (Z):   45.0°
#   Pitch (Y): 0.0°
#   Roll (X):  0.0°
#
# From Euler axis (45° about Z):
#   Yaw (Z):   45.0°
#   Pitch (Y): 0.0°
#   Roll (X):  -0.0°
#
# XZY Euler angles from ZYX:
#   Angle 1 (X): 20.8°
#   Angle 2 (Z): 50.8°
#   Angle 3 (Y): 14.5°
#   Order: XZY
#
# Converted back to ZYX order (same rotation):
#   Angle 1 (Z): 45.0°
#   Angle 2 (Y): 30.0°
#   Angle 3 (X): 15.0°
#   Order: ZYX
use brahe as bh;
use brahe::attitude::ToAttitude;
use nalgebra as na;
use std::f64::consts::PI;

fn main() {
    // Initialize from individual angles with ZYX sequence (yaw-pitch-roll)
    // 45° yaw, 30° pitch, 15° roll
    let ea_zyx = bh::EulerAngle::new(
        bh::EulerAngleOrder::ZYX,
        (45.0_f64).to_radians(),  // Yaw (Z)
        (30.0_f64).to_radians(),  // Pitch (Y)
        (15.0_f64).to_radians(),  // Roll (X)
        bh::AngleFormat::Radians
    );
    println!("ZYX Euler angles (yaw-pitch-roll):");
    println!("  Yaw (Z):   {:.1}°", ea_zyx.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea_zyx.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea_zyx.psi.to_degrees());
    println!("  Order: {:?}", ea_zyx.order);

    // Initialize from vector with XYZ sequence
    let angles_vec = na::SVector::<f64, 3>::new(
        (15.0_f64).to_radians(),
        (30.0_f64).to_radians(),
        (45.0_f64).to_radians()
    );
    let ea_xyz = bh::EulerAngle::from_vector(angles_vec, bh::EulerAngleOrder::XYZ, bh::AngleFormat::Radians);
    println!("\nXYZ Euler angles (from vector):");
    println!("  Angle 1 (X): {:.1}°", ea_xyz.phi.to_degrees());
    println!("  Angle 2 (Y): {:.1}°", ea_xyz.theta.to_degrees());
    println!("  Angle 3 (Z): {:.1}°", ea_xyz.psi.to_degrees());
    println!("  Order: {:?}", ea_xyz.order);

    // Simple rotation about single axis (45° about Z using ZYX)
    let ea_z_only = bh::EulerAngle::new(
        bh::EulerAngleOrder::ZYX,
        (45.0_f64).to_radians(),  // Z
        0.0,                       // Y
        0.0,                       // X
        bh::AngleFormat::Radians
    );
    println!("\nSingle-axis rotation (45° about Z using ZYX):");
    println!("  Yaw (Z):   {:.1}°", ea_z_only.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea_z_only.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea_z_only.psi.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::EulerAngle::from_quaternion(q, bh::EulerAngleOrder::ZYX);
    println!("\nFrom quaternion (45° about Z):");
    println!("  Yaw (Z):   {:.1}°", ea_from_q.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea_from_q.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea_from_q.psi.to_degrees());

    // Initialize from Rotation Matrix
    let rm = bh::RotationMatrix::Rz(45.0, bh::AngleFormat::Degrees);
    let ea_from_rm = bh::EulerAngle::from_rotation_matrix(rm, bh::EulerAngleOrder::ZYX);
    println!("\nFrom rotation matrix (45° about Z):");
    println!("  Yaw (Z):   {:.1}°", ea_from_rm.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea_from_rm.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea_from_rm.psi.to_degrees());

    // Initialize from Euler Axis
    let euler_axis = bh::EulerAxis::new(na::SVector::<f64, 3>::new(0.0, 0.0, 1.0), 45.0, bh::AngleFormat::Degrees);
    let ea_from_ea = bh::EulerAngle::from_euler_axis(euler_axis, bh::EulerAngleOrder::ZYX);
    println!("\nFrom Euler axis (45° about Z):");
    println!("  Yaw (Z):   {:.1}°", ea_from_ea.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea_from_ea.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea_from_ea.psi.to_degrees());

    // Initialize from one EulerAngle to another with different order
    // Start with XZY order
    let ea_xzy = bh::EulerAngle::from_euler_angle(ea_zyx, bh::EulerAngleOrder::XZY);
    println!("\nXZY Euler angles from ZYX:");
    println!("  Angle 1 (X): {:.1}°", ea_xzy.phi.to_degrees());
    println!("  Angle 2 (Z): {:.1}°", ea_xzy.theta.to_degrees());
    println!("  Angle 3 (Y): {:.1}°", ea_xzy.psi.to_degrees());
    println!("  Order: {:?}", ea_xzy.order);

    // Convert to ZYX order (same physical rotation, different representation)
    // Go through quaternion as intermediate representation
    let q_xzy = ea_xzy.to_quaternion();
    let ea_zyx_converted = bh::EulerAngle::from_quaternion(q_xzy, bh::EulerAngleOrder::ZYX);
    println!("\nConverted back to ZYX order (same rotation):");
    println!("  Angle 1 (Z): {:.1}°", ea_zyx_converted.phi.to_degrees());
    println!("  Angle 2 (Y): {:.1}°", ea_zyx_converted.theta.to_degrees());
    println!("  Angle 3 (X): {:.1}°", ea_zyx_converted.psi.to_degrees());
    println!("  Order: {:?}", ea_zyx_converted.order);
}

// Expected output:
// ZYX Euler angles (yaw-pitch-roll):
//   Yaw (Z):   45.0°
//   Pitch (Y): 30.0°
//   Roll (X):  15.0°
//   Order: ZYX
//
// XYZ Euler angles (from vector):
//   Angle 1 (X): 15.0°
//   Angle 2 (Y): 30.0°
//   Angle 3 (Z): 45.0°
//   Order: XYZ
//
// Single-axis rotation (45° about Z using ZYX):
//   Yaw (Z):   45.0°
//   Pitch (Y): 0.0°
//   Roll (X):  0.0°
//
// From quaternion (45° about Z):
//   Yaw (Z):   45.0°
//   Pitch (Y): 0.0°
//   Roll (X):  0.0°
//
// From rotation matrix (45° about Z):
//   Yaw (Z):   45.0°
//   Pitch (Y): 0.0°
//   Roll (X):  0.0°
//
// From Euler axis (45° about Z):
//   Yaw (Z):   45.0°
//   Pitch (Y): 0.0°
//   Roll (X):  -0.0°
//
// XZY Euler angles from ZYX:
//   Angle 1 (X): 20.8°
//   Angle 2 (Z): 50.8°
//   Angle 3 (Y): 14.5°
//   Order: XZY::132
//
// Converted back to ZYX order (same rotation):
//   Angle 1 (Z): 45.0°
//   Angle 2 (Y): 30.0°
//   Angle 3 (X): 15.0°
//   Order: ZYX::321

Conversions

Convert between Euler angles and other attitude representations:

import brahe as bh
import math

# Create Euler angles (ZYX: 45° yaw, 30° pitch, 15° roll)
ea = bh.EulerAngle(
    bh.EulerAngleOrder.ZYX,
    math.radians(45.0),
    math.radians(30.0),
    math.radians(15.0),
    bh.AngleFormat.RADIANS,
)

print("Original Euler angles (ZYX):")
print(f"  Yaw (Z):   {math.degrees(ea.phi):.1f}°")
print(f"  Pitch (Y): {math.degrees(ea.theta):.1f}°")
print(f"  Roll (X):  {math.degrees(ea.psi):.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 axis (axis-angle)
ea_axis = ea.to_euler_axis()
print("\nTo Euler axis:")
print(f"  Axis: [{ea_axis.axis[0]:.6f}, {ea_axis.axis[1]:.6f}, {ea_axis.axis[2]:.6f}]")
print(f"  Angle: {math.degrees(ea_axis.angle):.3f}°")

# Expected output:
# Original Euler angles (ZYX):
#   Yaw (Z):   45.0°
#   Pitch (Y): 30.0°
#   Roll (X):  15.0°

# To quaternion:
#   q = [0.871836, 0.214680, 0.188824, 0.397693]

# To rotation matrix:
#   [0.612372, 0.774519, -0.158494]
#   [-0.612372, 0.591506, 0.524519]
#   [0.500000, -0.224144, 0.836516]

# To Euler axis:
#   Axis: [0.438304, 0.385514, 0.811954]
#   Angle: 58.654°
use brahe as bh;
use brahe::attitude::ToAttitude;

fn main() {
    // Create Euler angles (ZYX: 45° yaw, 30° pitch, 15° roll)
    let ea = bh::EulerAngle::new(
        bh::EulerAngleOrder::ZYX,
        (45.0_f64).to_radians(),
        (30.0_f64).to_radians(),
        (15.0_f64).to_radians(),
        bh::AngleFormat::Radians
    );

    println!("Original Euler angles (ZYX):");
    println!("  Yaw (Z):   {:.1}°", ea.phi.to_degrees());
    println!("  Pitch (Y): {:.1}°", ea.theta.to_degrees());
    println!("  Roll (X):  {:.1}°", ea.psi.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 axis (axis-angle)
    let ea_axis = ea.to_euler_axis();
    println!("\nTo Euler axis:");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]", ea_axis.axis[0], ea_axis.axis[1], ea_axis.axis[2]);
    println!("  Angle: {:.3}°", ea_axis.angle.to_degrees());
}

// Expected output:
// Original Euler angles (ZYX):
//   Yaw (Z):   45.0°
//   Pitch (Y): 30.0°
//   Roll (X):  15.0°

// To quaternion:
//   q = [0.871836, 0.214680, 0.188824, 0.397693]

// To rotation matrix:
//   [0.612372, 0.774519, -0.158494]
//   [-0.612372, 0.591506, 0.524519]
//   [0.500000, -0.224144, 0.836516]

// To Euler axis:
//   Axis: [0.438304, 0.385514, 0.811954]
//   Angle: 58.654°

See Also