Skip to content

Rotation Matrices

A rotation matrix is a 3×3 matrix that transforms vectors from one coordinate frame to another. Also known as Direction Cosine Matrices (DCM).

Mathematical Representation

A rotation matrix is represented as:

\[ R = \begin{bmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{bmatrix} \]

A rotation matrix \(R\) satisfies the properties:

\[R^T R = I\]
\[\det(R) = 1\]

where \(I\) is the identity matrix.

Initialization

Rotation matrices can be created directly from elements, elementary rotations, or converted from other attitude representations:

import brahe as bh
import numpy as np
import math

# Initialize from 9 individual elements (row-major order)
# Identity rotation
rm_identity = bh.RotationMatrix(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
print("Identity rotation matrix:")
print(f"  [{rm_identity.r11:.3f}, {rm_identity.r12:.3f}, {rm_identity.r13:.3f}]")
print(f"  [{rm_identity.r21:.3f}, {rm_identity.r22:.3f}, {rm_identity.r23:.3f}]")
print(f"  [{rm_identity.r31:.3f}, {rm_identity.r32:.3f}, {rm_identity.r33:.3f}]")

# Initialize from a matrix of elements
matrix_elements = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
rm_from_matrix = bh.RotationMatrix.from_matrix(matrix_elements)
print("\nFrom matrix of elements:")
print(
    f"  [{rm_from_matrix.r11:.3f}, {rm_from_matrix.r12:.3f}, {rm_from_matrix.r13:.3f}]"
)
print(
    f"  [{rm_from_matrix.r21:.3f}, {rm_from_matrix.r22:.3f}, {rm_from_matrix.r23:.3f}]"
)
print(
    f"  [{rm_from_matrix.r31:.3f}, {rm_from_matrix.r32:.3f}, {rm_from_matrix.r33:.3f}]"
)

# Common rotation: 90° about X-axis
angle_x = 30
rm_x = bh.RotationMatrix.Rx(angle_x, bh.AngleFormat.DEGREES)
print(f"\n{angle_x}° rotation about X-axis:")
print(f"  [{rm_x.r11:.3f}, {rm_x.r12:.3f}, {rm_x.r13:.3f}]")
print(f"  [{rm_x.r21:.3f}, {rm_x.r22:.3f}, {rm_x.r23:.3f}]")
print(f"  [{rm_x.r31:.3f}, {rm_x.r32:.3f}, {rm_x.r33:.3f}]")

# Common rotation: 90° about Y-axis
angle_y = 60
rm_y = bh.RotationMatrix.Ry(angle_y, bh.AngleFormat.DEGREES)
print(f"\n{angle_y}° rotation about Y-axis:")
print(f"  [{rm_y.r11:.3f}, {rm_y.r12:.3f}, {rm_y.r13:.3f}]")
print(f"  [{rm_y.r21:.3f}, {rm_y.r22:.3f}, {rm_y.r23:.3f}]")
print(f"  [{rm_y.r31:.3f}, {rm_y.r32:.3f}, {rm_y.r33:.3f}]")

# Common rotation: 90° about Z-axis
angle_z = 45
rm_z = bh.RotationMatrix.Rz(angle_z, bh.AngleFormat.DEGREES)
print(f"\n{angle_z}° rotation about Z-axis:")
print(f"  [{rm_z.r11:.3f}, {rm_z.r12:.3f}, {rm_z.r13:.3f}]")
print(f"  [{rm_z.r21:.3f}, {rm_z.r22:.3f}, {rm_z.r23:.3f}]")
print(f"  [{rm_z.r31:.3f}, {rm_z.r32:.3f}, {rm_z.r33:.3f}]")

# Initialize from another representation (quaternion)
q = bh.Quaternion(
    math.cos(math.radians(angle_z) / 2), 0, 0, math.sin(math.radians(angle_z) / 2)
)  # 90° about Z-axis
rm_from_q = bh.RotationMatrix.from_quaternion(q)
print(f"\nFrom quaternion ({angle_z}° about Z-axis):")
print(f"  [{rm_from_q.r11:.3f}, {rm_from_q.r12:.3f}, {rm_from_q.r13:.3f}]")
print(f"  [{rm_from_q.r21:.3f}, {rm_from_q.r22:.3f}, {rm_from_q.r23:.3f}]")
print(f"  [{rm_from_q.r31:.3f}, {rm_from_q.r32:.3f}, {rm_from_q.r33:.3f}]")

# Initialize from Euler angles (ZYX sequence)
euler_angles = bh.EulerAngle(
    bh.EulerAngleOrder.ZYX, angle_z, 0, 0, bh.AngleFormat.DEGREES
)
rm_from_euler = bh.RotationMatrix.from_euler_angle(euler_angles)
print(f"\nFrom Euler angles ({angle_z}° about Z-axis):")
print(f"  [{rm_from_euler.r11:.3f}, {rm_from_euler.r12:.3f}, {rm_from_euler.r13:.3f}]")
print(f"  [{rm_from_euler.r21:.3f}, {rm_from_euler.r22:.3f}, {rm_from_euler.r23:.3f}]")
print(f"  [{rm_from_euler.r31:.3f}, {rm_from_euler.r32:.3f}, {rm_from_euler.r33:.3f}]")

# Initialize from Euler axis and angle
axis = np.array([0, 0, 1])  # Z-axis
euler_axis = bh.EulerAxis(axis, angle_z, bh.AngleFormat.DEGREES)
rm_from_axis_angle = bh.RotationMatrix.from_euler_axis(euler_axis)
print(f"\nFrom Euler axis ({angle_z}° about Z-axis):")
print(
    f"  [{rm_from_axis_angle.r11:.3f}, {rm_from_axis_angle.r12:.3f}, {rm_from_axis_angle.r13:.3f}]"
)
print(
    f"  [{rm_from_axis_angle.r21:.3f}, {rm_from_axis_angle.r22:.3f}, {rm_from_axis_angle.r23:.3f}]"
)
print(
    f"  [{rm_from_axis_angle.r31:.3f}, {rm_from_axis_angle.r32:.3f}, {rm_from_axis_angle.r33:.3f}]"
)

# Expected output:
# Identity rotation matrix:
#   [1.000, 0.000, 0.000]
#   [0.000, 1.000, 0.000]
#   [0.000, 0.000, 1.000]

# From matrix of elements:
#   [1.000, 0.000, 0.000]
#   [0.000, 1.000, 0.000]
#   [0.000, 0.000, 1.000]

# 30° rotation about X-axis:
#   [1.000, 0.000, 0.000]
#   [0.000, 0.866, 0.500]
#   [0.000, -0.500, 0.866]

# 60° rotation about Y-axis:
#   [0.500, 0.000, -0.866]
#   [0.000, 1.000, 0.000]
#   [0.866, 0.000, 0.500]

# 45° rotation about Z-axis:
#   [0.707, 0.707, 0.000]
#   [-0.707, 0.707, 0.000]
#   [0.000, 0.000, 1.000]

# From quaternion (45° about Z-axis):
#   [0.707, 0.707, 0.000]
#   [-0.707, 0.707, 0.000]
#   [0.000, 0.000, 1.000]

# From Euler angles (45° about Z-axis):
#   [0.707, 0.707, 0.000]
#   [-0.707, 0.707, 0.000]
#   [0.000, 0.000, 1.000]

# From Euler axis (45° about Z-axis):
#   [0.707, 0.707, 0.000]
#   [-0.707, 0.707, 0.000]
#   [0.000, 0.000, 1.000]
use brahe as bh;
use brahe::attitude::FromAttitude;
use brahe::AngleFormat;
use nalgebra as na;

fn main() {
    // Initialize from 9 individual elements (row-major order)
    // Identity rotation
    let rm_identity = bh::RotationMatrix::new(
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0
    ).unwrap();
    println!("Identity rotation matrix:");
    println!("  [{:.3}, {:.3}, {:.3}]", rm_identity[(0, 0)], rm_identity[(0, 1)], rm_identity[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_identity[(1, 0)], rm_identity[(1, 1)], rm_identity[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_identity[(2, 0)], rm_identity[(2, 1)], rm_identity[(2, 2)]);

    // Initialize from a matrix of elements
    let matrix_elements = na::SMatrix::<f64, 3, 3>::new(
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0
    );
    let rm_from_matrix = bh::RotationMatrix::from_matrix(matrix_elements).unwrap();
    println!("\nFrom matrix of elements:");
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_matrix[(0, 0)], rm_from_matrix[(0, 1)], rm_from_matrix[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_matrix[(1, 0)], rm_from_matrix[(1, 1)], rm_from_matrix[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_matrix[(2, 0)], rm_from_matrix[(2, 1)], rm_from_matrix[(2, 2)]);

    // Common rotation: 30° about X-axis
    let angle_x = 30.0;
    let rm_x = bh::RotationMatrix::Rx(angle_x, AngleFormat::Degrees);
    println!("\n{}° rotation about X-axis:", angle_x as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(0, 0)], rm_x[(0, 1)], rm_x[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(1, 0)], rm_x[(1, 1)], rm_x[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(2, 0)], rm_x[(2, 1)], rm_x[(2, 2)]);

    // Common rotation: 60° about Y-axis
    let angle_y = 60.0;
    let rm_y = bh::RotationMatrix::Ry(angle_y, AngleFormat::Degrees);
    println!("\n{}° rotation about Y-axis:", angle_y as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_y[(0, 0)], rm_y[(0, 1)], rm_y[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_y[(1, 0)], rm_y[(1, 1)], rm_y[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_y[(2, 0)], rm_y[(2, 1)], rm_y[(2, 2)]);

    // Common rotation: 45° about Z-axis
    let angle_z = 45.0;
    let rm_z = bh::RotationMatrix::Rz(angle_z, AngleFormat::Degrees);
    println!("\n{}° rotation about Z-axis:", angle_z as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(0, 0)], rm_z[(0, 1)], rm_z[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(1, 0)], rm_z[(1, 1)], rm_z[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(2, 0)], rm_z[(2, 1)], rm_z[(2, 2)]);

    // Initialize from another representation (quaternion)
    let q = bh::Quaternion::new(
        (angle_z.to_radians() / 2.0).cos(),
        0.0,
        0.0,
        (angle_z.to_radians() / 2.0).sin()
    );
    let rm_from_q = bh::RotationMatrix::from_quaternion(q);
    println!("\nFrom quaternion ({}° about Z-axis):", angle_z as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_q[(0, 0)], rm_from_q[(0, 1)], rm_from_q[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_q[(1, 0)], rm_from_q[(1, 1)], rm_from_q[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_q[(2, 0)], rm_from_q[(2, 1)], rm_from_q[(2, 2)]);

    // Initialize from Euler angles (ZYX sequence)
    let euler_angles = bh::EulerAngle::new(
        bh::EulerAngleOrder::ZYX,
        angle_z,
        0.0,
        0.0,
        AngleFormat::Degrees
    );
    let rm_from_euler = bh::RotationMatrix::from_euler_angle(euler_angles);
    println!("\nFrom Euler angles ({}° about Z-axis):", angle_z as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_euler[(0, 0)], rm_from_euler[(0, 1)], rm_from_euler[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_euler[(1, 0)], rm_from_euler[(1, 1)], rm_from_euler[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_euler[(2, 0)], rm_from_euler[(2, 1)], rm_from_euler[(2, 2)]);

    // Initialize from Euler axis and angle
    let axis = na::SVector::<f64, 3>::new(0.0, 0.0, 1.0); // Z-axis
    let euler_axis = bh::EulerAxis::new(axis, angle_z, AngleFormat::Degrees);
    let rm_from_axis_angle = bh::RotationMatrix::from_euler_axis(euler_axis);
    println!("\nFrom Euler axis ({}° about Z-axis):", angle_z as i32);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_axis_angle[(0, 0)], rm_from_axis_angle[(0, 1)], rm_from_axis_angle[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_axis_angle[(1, 0)], rm_from_axis_angle[(1, 1)], rm_from_axis_angle[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_from_axis_angle[(2, 0)], rm_from_axis_angle[(2, 1)], rm_from_axis_angle[(2, 2)]);
}

// Expected output:
// Identity rotation matrix:
//   [1.000, 0.000, 0.000]
//   [0.000, 1.000, 0.000]
//   [0.000, 0.000, 1.000]
//
// From matrix of elements:
//   [1.000, 0.000, 0.000]
//   [0.000, 1.000, 0.000]
//   [0.000, 0.000, 1.000]
//
// 30° rotation about X-axis:
//   [1.000, 0.000, 0.000]
//   [0.000, 0.866, 0.500]
//   [0.000, -0.500, 0.866]
//
// 60° rotation about Y-axis:
//   [0.500, 0.000, -0.866]
//   [0.000, 1.000, 0.000]
//   [0.866, 0.000, 0.500]
//
// 45° rotation about Z-axis:
//   [0.707, 0.707, 0.000]
//   [-0.707, 0.707, 0.000]
//   [0.000, 0.000, 1.000]
//
// From quaternion (45° about Z-axis):
//   [0.707, 0.707, 0.000]
//   [-0.707, 0.707, 0.000]
//   [0.000, 0.000, 1.000]
//
// From Euler angles (45° about Z-axis):
//   [0.707, 0.707, 0.000]
//   [-0.707, 0.707, 0.000]
//   [0.000, 0.000, 1.000]
//
// From Euler axis (45° about Z-axis):
//   [0.707, 0.707, 0.000]
//   [-0.707, 0.707, 0.000]
//   [0.000, 0.000, 1.000]

Tip

Brahe provides convenient methods to create rotation matrices for elementary rotations about the X, Y, and Z axes:

  • RotationMatrix.Rx(angle, format)
  • RotationMatrix.Ry(angle, format)
  • RotationMatrix.Rz(angle, format)

Output and Access

Access rotation matrix elements and convert to other formats:

import brahe as bh

# Create a rotation matrix (45° about Z-axis)
rm = bh.RotationMatrix.Rz(45, bh.AngleFormat.DEGREES)

# Access individual elements
print("Individual elements (row-by-row):")
print(f"  r11: {rm.r11:.6f}, r12: {rm.r12:.6f}, r13: {rm.r13:.6f}")
print(f"  r21: {rm.r21:.6f}, r22: {rm.r22:.6f}, r23: {rm.r23:.6f}")
print(f"  r31: {rm.r31:.6f}, r32: {rm.r32:.6f}, r33: {rm.r33:.6f}")
# String representation
print("\nString representation:")
print(f"  {rm}")

# Expected output:
# Individual elements (row-by-row):
#   r11: 0.707107, r12: 0.707107, r13: 0.000000
#   r21: -0.707107, r22: 0.707107, r23: 0.000000
#   r31: 0.000000, r32: 0.000000, r33: 1.000000

# String representation:
#   RotationMatrix:

#   ┌                                                             ┐
#   │  0.7071067811865476  0.7071067811865475                   0 │
#   │ -0.7071067811865475  0.7071067811865476                   0 │
#   │                   0                   0                   1 │
#   └                                                             ┘
use brahe as bh;

fn main() {
    // Create a rotation matrix (45° about Z-axis)
    let rm = bh::RotationMatrix::Rz(45.0, bh::AngleFormat::Degrees);

    // Access individual elements
    println!("Individual elements (row-by-row):");
    println!("  r11: {:.6}, r12: {:.6}, r13: {:.6}", rm[(0, 0)], rm[(0, 1)], rm[(0, 2)]);
    println!("  r21: {:.6}, r22: {:.6}, r23: {:.6}", rm[(1, 0)], rm[(1, 1)], rm[(1, 2)]);
    println!("  r31: {:.6}, r32: {:.6}, r33: {:.6}", rm[(2, 0)], rm[(2, 1)], rm[(2, 2)]);

    // String representation
    println!("\nString representation:");
    println!("  {:?}", rm);
}

// Expected output:
// Individual elements (row-by-row):
//   r11: 0.707107, r12: 0.707107, r13: 0.000000
//   r21: -0.707107, r22: 0.707107, r23: 0.000000
//   r31: 0.000000, r32: 0.000000, r33: 1.000000

// String representation:
//   RotationMatrix: 
// [[0.7071067811865476, -0.7071067811865475, 0.0], [0.7071067811865475, 0.7071067811865476, 0.0], [0.0, 0.0, 1.0]]

Operations

Rotation matrices support composition through matrix multiplication and vector rotation:

import brahe as bh
import numpy as np

# Create two rotation matrices
# 90° rotation about X-axis
rm_x = bh.RotationMatrix(1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0)

# 90° rotation about Z-axis
rm_z = bh.RotationMatrix(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0)

print("Rotation matrix X (90° about X):")
print(f"  [{rm_x.r11:.3f}, {rm_x.r12:.3f}, {rm_x.r13:.3f}]")
print(f"  [{rm_x.r21:.3f}, {rm_x.r22:.3f}, {rm_x.r23:.3f}]")
print(f"  [{rm_x.r31:.3f}, {rm_x.r32:.3f}, {rm_x.r33:.3f}]")

print("\nRotation matrix Z (90° about Z):")
print(f"  [{rm_z.r11:.3f}, {rm_z.r12:.3f}, {rm_z.r13:.3f}]")
print(f"  [{rm_z.r21:.3f}, {rm_z.r22:.3f}, {rm_z.r23:.3f}]")
print(f"  [{rm_z.r31:.3f}, {rm_z.r32:.3f}, {rm_z.r33:.3f}]")

# Matrix multiplication (compose rotations)
# Apply rm_x first, then rm_z
rm_composed = rm_z * rm_x
print("\nComposed rotation (X then Z):")
print(f"  [{rm_composed.r11:.3f}, {rm_composed.r12:.3f}, {rm_composed.r13:.3f}]")
print(f"  [{rm_composed.r21:.3f}, {rm_composed.r22:.3f}, {rm_composed.r23:.3f}]")
print(f"  [{rm_composed.r31:.3f}, {rm_composed.r32:.3f}, {rm_composed.r33:.3f}]")

# Transform a vector using rotation matrix
# Rotate vector [1, 0, 0] by 90° about Z-axis using matrix multiplication
R_z = rm_z.to_matrix()  # Get 3x3 numpy array
vector = np.array([1.0, 0.0, 0.0])
rotated = R_z @ vector  # Matrix-vector multiplication
print("\nVector transformation:")
print(f"  Original: [{vector[0]:.3f}, {vector[1]:.3f}, {vector[2]:.3f}]")
print(f"  Rotated:  [{rotated[0]:.3f}, {rotated[1]:.3f}, {rotated[2]:.3f}]")

# Transform another vector
vector2 = np.array([0.0, 1.0, 0.0])
rotated2 = R_z @ vector2
print(f"\n  Original: [{vector2[0]:.3f}, {vector2[1]:.3f}, {vector2[2]:.3f}]")
print(f"  Rotated:  [{rotated2[0]:.3f}, {rotated2[1]:.3f}, {rotated2[2]:.3f}]")

# Equality comparison
eq_result = rm_x == rm_z
neq_result = rm_x != rm_z
print("\nEquality comparison:")
print(f"  rm_x == rm_z: {eq_result}")
print(f"  rm_x != rm_z: {neq_result}")

# Expected output:
# Rotation matrix X (90° about X):
#   [1.000, 0.000, 0.000]
#   [0.000, 0.000, -1.000]
#   [0.000, 1.000, 0.000]

# Rotation matrix Z (90° about Z):
#   [0.000, -1.000, 0.000]
#   [1.000, 0.000, 0.000]
#   [0.000, 0.000, 1.000]

# Composed rotation (X then Z):
#   [0.000, 0.000, 1.000]
#   [1.000, 0.000, 0.000]
#   [0.000, 1.000, 0.000]

# Vector transformation:
#   Original: [1.000, 0.000, 0.000]
#   Rotated:  [0.000, 1.000, 0.000]

#   Original: [0.000, 1.000, 0.000]
#   Rotated:  [-1.000, 0.000, 0.000]

# Equality comparison:
#   rm_x == rm_z: False
#   rm_x != rm_z: True
use brahe as bh;
use nalgebra as na;

fn main() {
    // Create two rotation matrices
    // 90° rotation about X-axis
    let rm_x = bh::RotationMatrix::new(
        1.0, 0.0, 0.0,
        0.0, 0.0, -1.0,
        0.0, 1.0, 0.0
    ).unwrap();

    // 90° rotation about Z-axis
    let rm_z = bh::RotationMatrix::new(
        0.0, -1.0, 0.0,
        1.0, 0.0, 0.0,
        0.0, 0.0, 1.0
    ).unwrap();

    println!("Rotation matrix X (90° about X):");
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(0, 0)], rm_x[(0, 1)], rm_x[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(1, 0)], rm_x[(1, 1)], rm_x[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_x[(2, 0)], rm_x[(2, 1)], rm_x[(2, 2)]);

    println!("\nRotation matrix Z (90° about Z):");
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(0, 0)], rm_z[(0, 1)], rm_z[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(1, 0)], rm_z[(1, 1)], rm_z[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_z[(2, 0)], rm_z[(2, 1)], rm_z[(2, 2)]);

    // Matrix multiplication (compose rotations)
    // Apply rm_x first, then rm_z
    let rm_composed = rm_z * rm_x;
    println!("\nComposed rotation (X then Z):");
    println!("  [{:.3}, {:.3}, {:.3}]", rm_composed[(0, 0)], rm_composed[(0, 1)], rm_composed[(0, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_composed[(1, 0)], rm_composed[(1, 1)], rm_composed[(1, 2)]);
    println!("  [{:.3}, {:.3}, {:.3}]", rm_composed[(2, 0)], rm_composed[(2, 1)], rm_composed[(2, 2)]);

    // Transform a vector using rotation matrix
    // Rotate vector [1, 0, 0] by 90° about Z-axis using matrix multiplication
    let vector = na::SVector::<f64, 3>::new(1.0, 0.0, 0.0);
    let rotated = rm_z.to_matrix() * vector;  // Matrix-vector multiplication
    println!("\nVector transformation:");
    println!("  Original: [{:.3}, {:.3}, {:.3}]", vector[0], vector[1], vector[2]);
    println!("  Rotated:  [{:.3}, {:.3}, {:.3}]", rotated[0], rotated[1], rotated[2]);

    // Transform another vector
    let vector2 = na::SVector::<f64, 3>::new(0.0, 1.0, 0.0);
    let rotated2 = rm_z.to_matrix() * vector2;
    println!("\n  Original: [{:.3}, {:.3}, {:.3}]", vector2[0], vector2[1], vector2[2]);
    println!("  Rotated:  [{:.3}, {:.3}, {:.3}]", rotated2[0], rotated2[1], rotated2[2]);

    let eq_result = rm_x == rm_z;
    let neq_result = rm_x != rm_z;
    println!("\nEquality comparison:");
    println!("  rm_x == rm_z: {}", eq_result);
    println!("  rm_x != rm_z: {}", neq_result);
}

// Expected output:
// Rotation matrix X (90° about X):
//   [1.000, 0.000, 0.000]
//   [0.000, 0.000, -1.000]
//   [0.000, 1.000, 0.000]

// Rotation matrix Z (90° about Z):
//   [0.000, -1.000, 0.000]
//   [1.000, 0.000, 0.000]
//   [0.000, 0.000, 1.000]

// Composed rotation (X then Z):
//   [0.000, 0.000, 1.000]
//   [1.000, 0.000, 0.000]
//   [0.000, 1.000, 0.000]

// Vector transformation:
//   Original: [1.000, 0.000, 0.000]
//   Rotated:  [0.000, 1.000, 0.000]

//   Original: [0.000, 1.000, 0.000]
//   Rotated:  [-1.000, 0.000, 0.000]

// Equality comparison:
//   rm_x == rm_z: false
//   rm_x != rm_z: true

Conversions

Convert between rotation matrices and other attitude representations:

import brahe as bh
import math

# Create a rotation matrix (45° about Z-axis)
rm = bh.RotationMatrix.Rz(45, bh.AngleFormat.DEGREES)

print("Original 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 quaternion
q = rm.to_quaternion()
print("\nTo quaternion:")
print(f"  q = [{q.w:.6f}, {q.x:.6f}, {q.y:.6f}, {q.z:.6f}]")

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

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

# Convert to Euler axis (axis-angle)
ea = rm.to_euler_axis()
print("\nTo Euler axis:")
print(f"  Axis: [{ea.axis[0]:.6f}, {ea.axis[1]:.6f}, {ea.axis[2]:.6f}]")
print(f"  Angle: {math.degrees(ea.angle):.3f}°")

# Round-trip conversion test
rm_roundtrip = bh.RotationMatrix.from_quaternion(q)
print("\nRound-trip (RotationMatrix → Quaternion → RotationMatrix):")
print(f"  [{rm_roundtrip.r11:.6f}, {rm_roundtrip.r12:.6f}, {rm_roundtrip.r13:.6f}]")
print(f"  [{rm_roundtrip.r21:.6f}, {rm_roundtrip.r22:.6f}, {rm_roundtrip.r23:.6f}]")
print(f"  [{rm_roundtrip.r31:.6f}, {rm_roundtrip.r32:.6f}, {rm_roundtrip.r33:.6f}]")

# Expected output:
# Original rotation matrix:
#   [0.707107, -0.707107, 0.000000]
#   [0.707107, 0.707107, 0.000000]
#   [0.000000, 0.000000, 1.000000]
#
# To quaternion:
#   q = [0.923880, 0.000000, 0.000000, 0.382683]
#
# 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°
#
# To Euler axis:
#   Axis: [0.000000, 0.000000, 1.000000]
#   Angle: 45.000°
#
# Round-trip (RotationMatrix → Quaternion → RotationMatrix):
#   [0.707107, -0.707107, 0.000000]
#   [0.707107, 0.707107, 0.000000]
#   [0.000000, 0.000000, 1.000000]
use brahe as bh;
use brahe::attitude::FromAttitude;
use brahe::attitude::ToAttitude;

fn main() {
    // Create a rotation matrix (45° about Z-axis)
    let rm = bh::RotationMatrix::Rz(45.0, bh::AngleFormat::Degrees);

    println!("Original 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 quaternion
    let q = rm.to_quaternion();
    println!("\nTo quaternion:");
    println!("  q = [{:.6}, {:.6}, {:.6}, {:.6}]", q[0], q[1], q[2], q[3]);

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

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

    // Convert to Euler axis (axis-angle)
    let ea = rm.to_euler_axis();
    println!("\nTo Euler axis:");
    println!("  Axis: [{:.6}, {:.6}, {:.6}]", ea.axis[0], ea.axis[1], ea.axis[2]);
    println!("  Angle: {:.3}°", ea.angle.to_degrees());

    // Round-trip conversion test
    let rm_roundtrip = bh::RotationMatrix::from_quaternion(q);
    println!("\nRound-trip (RotationMatrix → Quaternion → RotationMatrix):");
    println!("  [{:.6}, {:.6}, {:.6}]", rm_roundtrip[(0, 0)], rm_roundtrip[(0, 1)], rm_roundtrip[(0, 2)]);
    println!("  [{:.6}, {:.6}, {:.6}]", rm_roundtrip[(1, 0)], rm_roundtrip[(1, 1)], rm_roundtrip[(1, 2)]);
    println!("  [{:.6}, {:.6}, {:.6}]", rm_roundtrip[(2, 0)], rm_roundtrip[(2, 1)], rm_roundtrip[(2, 2)]);
}

// Expected output:
// Original rotation matrix:
//   [0.707107, -0.707107, 0.000000]
//   [0.707107, 0.707107, 0.000000]
//   [0.000000, 0.000000, 1.000000]
//
// To quaternion:
//   q = [0.923880, 0.000000, 0.000000, 0.382683]
//
// 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°
//
// To Euler axis:
//   Axis: [0.000000, 0.000000, 1.000000]
//   Angle: 45.000°
//
// Round-trip (RotationMatrix → Quaternion → RotationMatrix):
//   [0.707107, -0.707107, 0.000000]
//   [0.707107, 0.707107, 0.000000]
//   [0.000000, 0.000000, 1.000000]

See Also