use crate::builder::{Set, Unset};
use crate::coordinate_systems::HasComponents;
use crate::directions::Bearing;
use crate::engineering::Orientation;
use crate::float_math::FloatMath;
use crate::{
Coordinate, CoordinateSystem,
systems::{EnuLike, EquivalentTo, FrdLike, NedLike, RightHandedXyzLike},
};
use crate::{LengthPossiblyPer, Vector3};
use core::fmt::{Display, Formatter};
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use core::{fmt, iter::Sum};
use typenum::{Integer, N1, N2, Z0};
use uom::ConstZero;
use uom::si::f64::{Acceleration, Angle, Length, Velocity};
use uom::si::{
acceleration::meter_per_second_squared, angle::radian, length::meter,
velocity::meter_per_second,
};
#[cfg(any(test, feature = "approx"))]
use {
crate::Point3,
approx::{AbsDiffEq, RelativeEq},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(doc)]
use crate::{
math::RigidBodyTransform,
systems::BearingDefined,
vector::{AccelerationVector, LengthVector, VelocityVector},
};
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Vector<In, Time = Z0>
where
Time: Integer,
{
pub(crate) inner: Vector3,
#[cfg_attr(feature = "serde", serde(skip))]
system: PhantomData<In>,
#[cfg_attr(feature = "serde", serde(skip))]
unit: PhantomData<LengthPossiblyPer<Time>>,
}
#[doc(hidden)]
pub trait LengthBasedComponents<In, Time>: private::Sealed
where
Time: Integer,
{
fn from_cartesian(xyz: [LengthPossiblyPer<Time>; 3]) -> Self;
fn to_cartesian(&self) -> [LengthPossiblyPer<Time>; 3];
fn recast_to_length(v: LengthPossiblyPer<Time>) -> Length;
}
impl<In> LengthBasedComponents<In, Z0> for Vector<In, Z0> {
fn from_cartesian([x, y, z]: [Length; 3]) -> Self {
Self::from_nalgebra_vector(Vector3::new(
x.get::<meter>(),
y.get::<meter>(),
z.get::<meter>(),
))
}
fn to_cartesian(&self) -> [Length; 3] {
[
Length::new::<meter>(self.inner.x),
Length::new::<meter>(self.inner.y),
Length::new::<meter>(self.inner.z),
]
}
fn recast_to_length(v: LengthPossiblyPer<Z0>) -> Length {
core::convert::identity(v)
}
}
impl<In> LengthBasedComponents<In, N1> for Vector<In, N1> {
fn from_cartesian([x, y, z]: [Velocity; 3]) -> Self {
Self::from_nalgebra_vector(Vector3::new(
x.get::<meter_per_second>(),
y.get::<meter_per_second>(),
z.get::<meter_per_second>(),
))
}
fn to_cartesian(&self) -> [Velocity; 3] {
[
Velocity::new::<meter_per_second>(self.inner.x),
Velocity::new::<meter_per_second>(self.inner.y),
Velocity::new::<meter_per_second>(self.inner.z),
]
}
fn recast_to_length(v: LengthPossiblyPer<N1>) -> Length {
Length::new::<meter>(v.get::<meter_per_second>())
}
}
impl<In> LengthBasedComponents<In, N2> for Vector<In, N2> {
fn from_cartesian([x, y, z]: [Acceleration; 3]) -> Self {
Self::from_nalgebra_vector(Vector3::new(
x.get::<meter_per_second_squared>(),
y.get::<meter_per_second_squared>(),
z.get::<meter_per_second_squared>(),
))
}
fn to_cartesian(&self) -> [Acceleration; 3] {
[
Acceleration::new::<meter_per_second_squared>(self.inner.x),
Acceleration::new::<meter_per_second_squared>(self.inner.y),
Acceleration::new::<meter_per_second_squared>(self.inner.z),
]
}
fn recast_to_length(v: LengthPossiblyPer<N2>) -> Length {
Length::new::<meter>(v.get::<meter_per_second_squared>())
}
}
mod private {
pub trait Sealed {}
impl<In> Sealed for super::Vector<In, typenum::Z0> {}
impl<In> Sealed for super::Vector<In, typenum::N1> {}
impl<In> Sealed for super::Vector<In, typenum::N2> {}
}
impl<In, Time: Integer> Clone for Vector<In, Time> {
fn clone(&self) -> Self {
*self
}
}
impl<In, Time: Integer> Copy for Vector<In, Time> {}
#[macro_export]
macro_rules! vector {
($x:tt = $xx:expr, $y:tt = $yy:expr, $z:tt = $zz:expr $(,)?) => {
vector!($x = $xx, $y = $yy, $z = $zz; in _)
};
(n = $n:expr, e = $e:expr, d = $d:expr; in $in:ty) => {
$crate::Vector::<$in, _>::build($crate::systems::NedComponents {
north: $n.into(),
east: $e.into(),
down: $d.into(),
})
};
(e = $e:expr, n = $n:expr, u = $u:expr; in $in:ty) => {
$crate::Vector::<$in, _>::build($crate::systems::EnuComponents {
east: $e.into(),
north: $n.into(),
up: $u.into(),
})
};
(f = $f:expr, r = $r:expr, d = $d:expr; in $in:ty) => {
$crate::Vector::<$in, _>::build($crate::systems::FrdComponents {
front: $f.into(),
right: $r.into(),
down: $d.into(),
})
};
(x = $x:expr, y = $y:expr, z = $z:expr; in $in:ty) => {
$crate::Vector::<$in, _>::build($crate::systems::XyzComponents {
x: $x.into(),
y: $y.into(),
z: $z.into(),
})
};
}
impl<In, Time> Vector<In, Time>
where
Time: Integer,
Self: LengthBasedComponents<In, Time>,
{
pub(crate) fn from_nalgebra_vector(value: Vector3) -> Self {
Self {
inner: value,
system: PhantomData::<In>,
unit: PhantomData::<LengthPossiblyPer<Time>>,
}
}
pub fn build(components: <In::Convention as HasComponents<Time>>::Components) -> Self
where
In: CoordinateSystem,
In::Convention: HasComponents<Time>,
{
let [x, y, z] = components.into();
#[allow(deprecated)]
Self::from_cartesian(x, y, z)
}
pub fn builder() -> Builder<In, Unset, Unset, Unset, Time>
where
In: CoordinateSystem,
{
Builder::default()
}
#[deprecated = "prefer `Vector::builder` to avoid risk of argument order confusion"]
pub fn from_cartesian(
x: impl Into<LengthPossiblyPer<Time>>,
y: impl Into<LengthPossiblyPer<Time>>,
z: impl Into<LengthPossiblyPer<Time>>,
) -> Self {
LengthBasedComponents::from_cartesian([x.into(), y.into(), z.into()])
}
pub fn from_spherical(
radius: impl Into<LengthPossiblyPer<Time>>,
polar: impl Into<Angle>,
azimuth: impl Into<Angle>,
) -> Self {
let radius: LengthPossiblyPer<Time> = radius.into();
let azimuth = azimuth.into();
let polar = polar.into();
let x = radius
* FloatMath::sin(polar.get::<radian>())
* FloatMath::cos(azimuth.get::<radian>());
let y = radius
* FloatMath::sin(polar.get::<radian>())
* FloatMath::sin(azimuth.get::<radian>());
let z = radius * FloatMath::cos(polar.get::<radian>());
#[allow(deprecated)]
Self::from_cartesian(x, y, z)
}
pub fn from_bearing(bearing: Bearing<In>, range: impl Into<LengthPossiblyPer<Time>>) -> Self
where
In: crate::systems::BearingDefined,
{
let (polar, azimuth) = In::bearing_to_spherical(bearing);
Self::from_spherical(range.into(), polar, azimuth)
}
#[must_use]
pub fn with_same_components_in<NewIn>(self) -> Vector<NewIn, Time> {
Vector {
inner: self.inner,
system: PhantomData::<NewIn>,
unit: self.unit,
}
}
#[must_use]
pub fn cast<NewIn>(self) -> Vector<NewIn, Time>
where
In: EquivalentTo<NewIn>,
{
self.with_same_components_in::<NewIn>()
}
#[must_use]
pub fn normalized(&self) -> Self {
Self::from_nalgebra_vector(self.inner.normalize())
}
#[must_use]
pub fn dot(&self, rhs: &Self) -> f64 {
self.inner.dot(&rhs.inner)
}
#[must_use]
pub fn lerp(&self, rhs: &Self, t: f64) -> Self {
Self {
inner: self.inner.lerp(&rhs.inner, t),
system: self.system,
unit: self.unit,
}
}
#[must_use]
pub fn bearing_at_origin(&self) -> Option<Bearing<In>>
where
In: crate::systems::BearingDefined,
{
let [x, y, z] = self.to_cartesian();
let x = Vector::recast_to_length(x);
let y = Vector::recast_to_length(y);
let z = Vector::recast_to_length(z);
#[allow(deprecated)]
Coordinate::from_cartesian(x, y, z).bearing_from_origin()
}
#[must_use]
pub fn orientation_at_origin(&self, roll: impl Into<Angle>) -> Option<Orientation<In>> {
let [x, y, z] = self.to_cartesian();
let x = Vector::recast_to_length(x);
let y = Vector::recast_to_length(y);
let z = Vector::recast_to_length(z);
if x == Length::ZERO && y == Length::ZERO && z == Length::ZERO {
return None;
}
let yaw = Angle::new::<radian>(FloatMath::atan2(y.get::<meter>(), x.get::<meter>()));
let pitch = Angle::new::<radian>(FloatMath::atan2(
-z.get::<meter>(),
FloatMath::sqrt(
FloatMath::powi(x.get::<meter>(), 2) + FloatMath::powi(y.get::<meter>(), 2),
),
));
let roll = roll.into();
Some(
Orientation::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build(),
)
}
#[must_use]
pub fn zero() -> Self {
Self {
inner: Vector3::zeros(),
system: PhantomData::<In>,
unit: PhantomData::<LengthPossiblyPer<Time>>,
}
}
}
impl<In> From<Coordinate<In>> for Vector<In, Z0> {
fn from(value: Coordinate<In>) -> Self {
Self::from_nalgebra_vector(value.point.coords)
}
}
impl<In, Time> Default for Vector<In, Time>
where
Time: Integer,
Self: LengthBasedComponents<In, Time>,
{
fn default() -> Self {
Self::zero()
}
}
macro_rules! accessors {
{
$convention:ident
using $x:ident, $y:ident, $z:ident
+ $x_ax:ident, $y_ax:ident, $z_ax:ident
} => {
impl<In, Time: typenum::Integer> Vector<In, Time> where In: CoordinateSystem<Convention = $convention>, Self: LengthBasedComponents<In, Time> {
#[must_use]
pub fn $x(&self) -> LengthPossiblyPer<Time> {
let cartesian = LengthBasedComponents::to_cartesian(self);
cartesian[0]
}
#[must_use]
pub fn $y(&self) -> LengthPossiblyPer<Time> {
let cartesian = LengthBasedComponents::to_cartesian(self);
cartesian[1]
}
#[must_use]
pub fn $z(&self) -> LengthPossiblyPer<Time> {
let cartesian = LengthBasedComponents::to_cartesian(self);
cartesian[2]
}
#[must_use]
pub fn $x_ax() -> Vector<In, Time> {
Self {
inner: *Vector3::x_axis(),
system: core::marker::PhantomData::<In>,
unit: core::marker::PhantomData::<LengthPossiblyPer<Time>>,
}
}
#[must_use]
pub fn $y_ax() -> Vector<In, Time> {
Self {
inner: *Vector3::y_axis(),
system: core::marker::PhantomData::<In>,
unit: core::marker::PhantomData::<LengthPossiblyPer<Time>>,
}
}
#[must_use]
pub fn $z_ax() -> Vector<In, Time> {
Self {
inner: *Vector3::z_axis(),
system: core::marker::PhantomData::<In>,
unit: core::marker::PhantomData::<LengthPossiblyPer<Time>>,
}
}
}
};
}
accessors!(RightHandedXyzLike using x, y, z + x_axis, y_axis, z_axis);
accessors!(NedLike using ned_north, ned_east, ned_down + ned_north_axis, ned_east_axis, ned_down_axis);
accessors!(FrdLike using frd_front, frd_right, frd_down + frd_front_axis, frd_right_axis, frd_down_axis);
accessors!(EnuLike using enu_east, enu_north, enu_up + enu_east_axis, enu_north_axis, enu_up_axis);
impl<In> Vector<In, Z0> {
#[doc(alias = "components")]
#[must_use]
pub fn to_cartesian(&self) -> [Length; 3] {
LengthBasedComponents::to_cartesian(self)
}
#[doc(alias = "norm")]
#[doc(alias = "distance")]
#[doc(alias = "length")]
#[must_use]
pub fn magnitude(&self) -> Length {
Length::new::<meter>(self.inner.norm())
}
}
impl<In> Vector<In, N1> {
#[doc(alias = "components")]
#[must_use]
pub fn to_cartesian(&self) -> [Velocity; 3] {
LengthBasedComponents::to_cartesian(self)
}
#[doc(alias = "norm")]
#[doc(alias = "speed")]
#[must_use]
pub fn magnitude(&self) -> Velocity {
Velocity::new::<meter_per_second>(self.inner.norm())
}
}
impl<In> Vector<In, N2> {
#[doc(alias = "components")]
#[must_use]
pub fn to_cartesian(&self) -> [Acceleration; 3] {
LengthBasedComponents::to_cartesian(self)
}
#[doc(alias = "norm")]
#[doc(alias = "acceleration")]
#[must_use]
pub fn magnitude(&self) -> Acceleration {
Acceleration::new::<meter_per_second_squared>(self.inner.norm())
}
}
impl<In, Time: Integer> Neg for Vector<In, Time> {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
inner: -self.inner,
system: self.system,
unit: self.unit,
}
}
}
impl<In, Time: Integer> Add<Self> for Vector<In, Time> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
inner: self.inner + rhs.inner,
system: self.system,
unit: self.unit,
}
}
}
impl<In, Time: Integer> AddAssign<Self> for Vector<In, Time> {
fn add_assign(&mut self, rhs: Self) {
self.inner += rhs.inner;
}
}
impl<In, Time: Integer> Sub<Self> for Vector<In, Time> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
inner: self.inner - rhs.inner,
system: self.system,
unit: self.unit,
}
}
}
impl<In, Time: Integer> SubAssign<Self> for Vector<In, Time> {
fn sub_assign(&mut self, rhs: Self) {
self.inner -= rhs.inner;
}
}
impl<In, Time: Integer> Sum for Vector<In, Time>
where
Self: LengthBasedComponents<In, Time>,
{
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::zero(), |sum, v| sum + v)
}
}
impl<In> Mul<uom::si::f64::Time> for Vector<In, N2> {
type Output = Vector<In, N1>;
fn mul(self, duration: uom::si::f64::Time) -> Self::Output {
LengthBasedComponents::from_cartesian(self.to_cartesian().map(|v| v * duration))
}
}
impl<In> Mul<uom::si::f64::Time> for Vector<In, N1> {
type Output = Vector<In, Z0>;
fn mul(self, duration: uom::si::f64::Time) -> Self::Output {
LengthBasedComponents::from_cartesian(self.to_cartesian().map(|v| v * duration))
}
}
impl<In, Time: Integer> Mul<f64> for Vector<In, Time> {
type Output = Self;
fn mul(self, scalar: f64) -> Self::Output {
Self {
inner: self.inner * scalar,
system: self.system,
unit: self.unit,
}
}
}
impl<In, Time: Integer> Div<f64> for Vector<In, Time> {
type Output = Self;
fn div(self, scalar: f64) -> Self::Output {
Self {
inner: self.inner / scalar,
system: self.system,
unit: self.unit,
}
}
}
impl<In, Time: Integer> Div<Length> for Vector<In, Time> {
type Output = Self;
fn div(self, rhs: Length) -> Self::Output {
self / rhs.get::<meter>()
}
}
impl<In, Time: Integer> Mul<Length> for Vector<In, Time> {
type Output = Self;
fn mul(self, rhs: Length) -> Self::Output {
self * rhs.get::<meter>()
}
}
impl<In, Time: Integer> PartialEq<Self> for Vector<In, Time> {
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}
impl<In, Time: Integer> Display for Vector<In, Time> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[cfg(any(test, feature = "approx"))]
impl<In, Time: Integer> AbsDiffEq<Self> for Vector<In, Time> {
type Epsilon = <f64 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
0.1
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.inner.abs_diff_eq(&other.inner, epsilon)
}
}
#[cfg(any(test, feature = "approx"))]
impl<In, Time: Integer> RelativeEq for Vector<In, Time> {
fn default_max_relative() -> Self::Epsilon {
Point3::default_max_relative()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.inner.relative_eq(&other.inner, epsilon, max_relative)
}
}
#[derive(Debug)]
#[must_use]
pub struct Builder<In, X, Y, Z, Time = Z0>
where
Time: Integer,
{
under_construction: Vector<In, Time>,
set: (PhantomData<X>, PhantomData<Y>, PhantomData<Z>),
}
impl<In, Time> Default for Builder<In, Unset, Unset, Unset, Time>
where
Time: Integer,
Vector<In, Time>: LengthBasedComponents<In, Time>,
{
fn default() -> Self {
Self {
under_construction: Vector::default(),
set: (PhantomData, PhantomData, PhantomData),
}
}
}
impl<In, Time: Integer> Builder<In, Set, Set, Set, Time> {
#[must_use]
pub fn build(self) -> Vector<In, Time> {
self.under_construction
}
}
macro_rules! constructor {
($like:ident, [$x:ident, $y:ident, $z:ident]) => {
impl<In, X, Y, Z, Time> Builder<In, X, Y, Z, Time>
where
In: CoordinateSystem<Convention = $like>,
Time: typenum::Integer,
Vector<In, Time>: LengthBasedComponents<In, Time>,
{
pub fn $x(
mut self,
value: impl Into<LengthPossiblyPer<Time>>,
) -> Builder<In, Set, Y, Z, Time> {
let mut cartesian = LengthBasedComponents::to_cartesian(&self.under_construction);
cartesian[0] = value.into();
self.under_construction = LengthBasedComponents::from_cartesian(cartesian);
Builder {
under_construction: self.under_construction,
set: (PhantomData::<Set>, self.set.1, self.set.2),
}
}
pub fn $y(
mut self,
value: impl Into<LengthPossiblyPer<Time>>,
) -> Builder<In, X, Set, Z, Time> {
let mut cartesian = LengthBasedComponents::to_cartesian(&self.under_construction);
cartesian[1] = value.into();
self.under_construction = LengthBasedComponents::from_cartesian(cartesian);
Builder {
under_construction: self.under_construction,
set: (self.set.0, PhantomData::<Set>, self.set.2),
}
}
pub fn $z(
mut self,
value: impl Into<LengthPossiblyPer<Time>>,
) -> Builder<In, X, Y, Set, Time> {
let mut cartesian = LengthBasedComponents::to_cartesian(&self.under_construction);
cartesian[2] = value.into();
self.under_construction = LengthBasedComponents::from_cartesian(cartesian);
Builder {
under_construction: self.under_construction,
set: (self.set.0, self.set.1, PhantomData::<Set>),
}
}
}
};
}
constructor!(RightHandedXyzLike, [x, y, z]);
constructor!(NedLike, [ned_north, ned_east, ned_down]);
constructor!(FrdLike, [frd_front, frd_right, frd_down]);
constructor!(EnuLike, [enu_east, enu_north, enu_up]);
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinate;
use crate::systems::EquivalentTo;
use approx::assert_abs_diff_eq;
use typenum::{N1, N2, Z0};
use uom::si::f64::{Acceleration, Angle, Length, Velocity};
use uom::si::{
acceleration::meter_per_second_squared, angle::degree, length::meter,
velocity::meter_per_second,
};
system!(struct TestFrd using FRD);
system!(struct TestNed using NED);
system!(struct TestEnu using ENU);
system!(struct TestXyz using right-handed XYZ);
system!(struct TestFrd2 using FRD);
unsafe impl EquivalentTo<TestFrd> for TestFrd2 {}
unsafe impl EquivalentTo<TestFrd2> for TestFrd {}
fn m(meters: f64) -> Length {
Length::new::<meter>(meters)
}
fn mps(meters_per_second: f64) -> Velocity {
Velocity::new::<meter_per_second>(meters_per_second)
}
fn mps2(meters_per_second_squared: f64) -> Acceleration {
Acceleration::new::<meter_per_second_squared>(meters_per_second_squared)
}
mod length_vectors {
use super::*;
#[test]
fn constructor_from_cartesian_works() {
#[allow(deprecated)]
let v = Vector::<TestFrd, Z0>::from_cartesian(m(1.0), m(2.0), m(3.0));
assert_eq!(v.to_cartesian(), [m(1.0), m(2.0), m(3.0)]);
}
#[test]
fn builder_pattern_works() {
let v = Vector::<TestFrd>::builder()
.frd_front(m(1.0))
.frd_right(m(2.0))
.frd_down(m(3.0))
.build();
assert_eq!(v.frd_front(), m(1.0));
assert_eq!(v.frd_right(), m(2.0));
assert_eq!(v.frd_down(), m(3.0));
}
#[test]
fn vector_macro_works() {
let v = vector!(f = m(1.0), r = m(2.0), d = m(3.0); in TestFrd);
assert_eq!(v.frd_front(), m(1.0));
assert_eq!(v.frd_right(), m(2.0));
assert_eq!(v.frd_down(), m(3.0));
}
#[test]
fn build_with_components_works() {
use crate::systems::FrdComponents;
let v = Vector::<TestFrd>::build(FrdComponents {
front: m(1.0),
right: m(2.0),
down: m(3.0),
});
assert_eq!(v.frd_front(), m(1.0));
assert_eq!(v.frd_right(), m(2.0));
assert_eq!(v.frd_down(), m(3.0));
}
#[test]
fn from_spherical_works() {
let v = Vector::<TestFrd>::from_spherical(
m(10.0),
Angle::new::<degree>(90.0), Angle::new::<degree>(0.0), );
assert_abs_diff_eq!(v.frd_front().get::<meter>(), 10.0, epsilon = 1e-10);
assert_abs_diff_eq!(v.frd_right().get::<meter>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(v.frd_down().get::<meter>(), 0.0, epsilon = 1e-10);
}
#[test]
fn magnitude_works() {
let v = vector!(f = m(3.0), r = m(4.0), d = m(0.0); in TestFrd);
assert_eq!(v.magnitude(), m(5.0));
}
#[test]
fn axis_vectors_work() {
let front = Vector::<TestFrd>::frd_front_axis();
let right = Vector::<TestFrd>::frd_right_axis();
let down = Vector::<TestFrd>::frd_down_axis();
assert_eq!(front.frd_front(), m(1.0));
assert_eq!(front.frd_right(), m(0.0));
assert_eq!(front.frd_down(), m(0.0));
assert_eq!(right.frd_front(), m(0.0));
assert_eq!(right.frd_right(), m(1.0));
assert_eq!(right.frd_down(), m(0.0));
assert_eq!(down.frd_front(), m(0.0));
assert_eq!(down.frd_right(), m(0.0));
assert_eq!(down.frd_down(), m(1.0));
}
#[test]
fn arithmetic_operations_work() {
let v1 = vector!(f = m(1.0), r = m(2.0), d = m(3.0); in TestFrd);
let v2 = vector!(f = m(4.0), r = m(5.0), d = m(6.0); in TestFrd);
let sum = v1 + v2;
assert_eq!(sum.to_cartesian(), [m(5.0), m(7.0), m(9.0)]);
let diff = v2 - v1;
assert_eq!(diff.to_cartesian(), [m(3.0), m(3.0), m(3.0)]);
let neg = -v1;
assert_eq!(neg.to_cartesian(), [m(-1.0), m(-2.0), m(-3.0)]);
let scaled = v1 * 2.0;
assert_eq!(scaled.to_cartesian(), [m(2.0), m(4.0), m(6.0)]);
}
#[test]
fn zero_vector_works() {
let v = Vector::<TestFrd>::zero();
assert_eq!(v.to_cartesian(), [m(0.0), m(0.0), m(0.0)]);
}
#[test]
fn lerp_works() {
let v1 = vector!(f = m(0.0), r = m(0.0), d = m(0.0); in TestFrd);
let v2 = vector!(f = m(10.0), r = m(20.0), d = m(30.0); in TestFrd);
let mid = v1.lerp(&v2, 0.5);
assert_eq!(mid.to_cartesian(), [m(5.0), m(10.0), m(15.0)]);
}
#[test]
fn cast_equivalent_coordinate_systems() {
let v_frd = vector!(f = m(1.0), r = m(2.0), d = m(3.0); in TestFrd);
let v_frd2: Vector<TestFrd2> = v_frd.cast();
assert_eq!(v_frd2.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn with_same_components_in_works() {
let v_frd = vector!(f = m(1.0), r = m(2.0), d = m(3.0); in TestFrd);
let v_ned: Vector<TestNed> = v_frd.with_same_components_in();
assert_eq!(v_ned.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn ned_accessors_work() {
let v = vector!(n = m(1.0), e = m(2.0), d = m(3.0); in TestNed);
assert_eq!(v.ned_north(), m(1.0));
assert_eq!(v.ned_east(), m(2.0));
assert_eq!(v.ned_down(), m(3.0));
let north = Vector::<TestNed>::ned_north_axis();
let east = Vector::<TestNed>::ned_east_axis();
let down = Vector::<TestNed>::ned_down_axis();
assert_eq!(north.ned_north(), m(1.0));
assert_eq!(east.ned_east(), m(1.0));
assert_eq!(down.ned_down(), m(1.0));
}
#[test]
fn enu_accessors_work() {
let v = vector!(e = m(1.0), n = m(2.0), u = m(3.0); in TestEnu);
assert_eq!(v.enu_east(), m(1.0));
assert_eq!(v.enu_north(), m(2.0));
assert_eq!(v.enu_up(), m(3.0));
}
#[test]
fn xyz_accessors_work() {
let v = vector!(x = m(1.0), y = m(2.0), z = m(3.0); in TestXyz);
assert_eq!(v.x(), m(1.0));
assert_eq!(v.y(), m(2.0));
assert_eq!(v.z(), m(3.0));
}
#[test]
fn orientation_at_origin_zero_vector_returns_none() {
let v = Vector::<TestXyz>::zero();
assert_eq!(v.orientation_at_origin(Angle::ZERO), None);
}
#[test]
fn orientation_at_origin_positive_x_axis() {
let v = vector!(x = m(1.0), y = m(0.0), z = m(0.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_positive_y_axis() {
let v = vector!(x = m(0.0), y = m(1.0), z = m(0.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 90.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_positive_z_axis() {
let v = vector!(x = m(0.0), y = m(0.0), z = m(1.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), -90.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_negative_x_axis() {
let v = vector!(x = m(-1.0), y = m(0.0), z = m(0.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>().abs(), 180.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_45_degree_xy_plane() {
let v = vector!(x = m(1.0), y = m(1.0), z = m(0.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 45.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_45_degree_xz_plane() {
let v = vector!(x = m(1.0), y = m(0.0), z = m(1.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), -45.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_general_3d_vector() {
let v = vector!(x = m(3.0), y = m(4.0), z = m(5.0); in TestXyz);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 53.13010235415598, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), -45.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
let frd = vector!(f = v.magnitude(), r = m(0.), d = m(0.); in TestFrd);
let pose = crate::engineering::Pose::new(Coordinate::origin(), orientation);
let pose = unsafe { pose.map_as_zero_in::<TestFrd>() };
let v2 = pose.inverse_transform(frd);
assert_abs_diff_eq!(v, v2);
}
#[test]
fn orientation_at_origin_ned() {
let v = vector!(n = m(0.0), e = m(1.0), d = m(-1.0); in TestNed);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 90.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), 45.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
#[test]
fn orientation_at_origin_enu_east_vector() {
let v = vector!(e = m(0.0), n = m(1.0), u = m(1.0); in TestEnu);
let orientation = v
.orientation_at_origin(Angle::ZERO)
.expect("non-zero vector");
let (yaw, pitch, roll) = orientation.to_tait_bryan_angles();
assert_abs_diff_eq!(yaw.get::<degree>(), 90.0, epsilon = 1e-10);
assert_abs_diff_eq!(pitch.get::<degree>(), -45.0, epsilon = 1e-10);
assert_abs_diff_eq!(roll.get::<degree>(), 0.0, epsilon = 1e-10);
}
}
mod velocity_vectors {
use super::*;
#[test]
fn constructor_from_cartesian_works() {
#[allow(deprecated)]
let v = Vector::<TestFrd, N1>::from_cartesian(mps(1.0), mps(2.0), mps(3.0));
assert_eq!(v.to_cartesian(), [mps(1.0), mps(2.0), mps(3.0)]);
}
#[test]
fn build_with_components_works() {
use crate::systems::FrdComponents;
let v = Vector::<TestFrd, N1>::build(FrdComponents {
front: mps(1.0),
right: mps(2.0),
down: mps(3.0),
});
let cartesian = v.to_cartesian();
assert_eq!(cartesian, [mps(1.0), mps(2.0), mps(3.0)]);
}
#[test]
fn builder_pattern_works() {
let v = Vector::<TestFrd, N1>::builder()
.frd_front(mps(1.0))
.frd_right(mps(2.0))
.frd_down(mps(3.0))
.build();
assert_eq!(v.frd_front(), mps(1.0));
assert_eq!(v.frd_right(), mps(2.0));
assert_eq!(v.frd_down(), mps(3.0));
}
#[test]
fn magnitude_works() {
let v = vector!(f = mps(3.0), r = mps(4.0), d = mps(0.0); in TestFrd);
assert_eq!(v.magnitude(), mps(5.0));
}
#[test]
fn from_spherical_works() {
let v = Vector::<TestFrd, N1>::from_spherical(
mps(10.0),
Angle::new::<degree>(90.0), Angle::new::<degree>(0.0), );
let cartesian = v.to_cartesian();
assert_abs_diff_eq!(
cartesian[0].get::<meter_per_second>(),
10.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(cartesian[1].get::<meter_per_second>(), 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(cartesian[2].get::<meter_per_second>(), 0.0, epsilon = 1e-10);
}
#[test]
fn axis_vectors_work() {
let front = Vector::<TestFrd, N1>::frd_front_axis();
let right = Vector::<TestFrd, N1>::frd_right_axis();
let down = Vector::<TestFrd, N1>::frd_down_axis();
let front_cart = front.to_cartesian();
let right_cart = right.to_cartesian();
let down_cart = down.to_cartesian();
assert_eq!(front_cart, [mps(1.0), mps(0.0), mps(0.0)]);
assert_eq!(right_cart, [mps(0.0), mps(1.0), mps(0.0)]);
assert_eq!(down_cart, [mps(0.0), mps(0.0), mps(1.0)]);
}
#[test]
fn arithmetic_operations_work() {
let v1 = vector!(f = mps(1.0), r = mps(2.0), d = mps(3.0); in TestFrd);
let v2 = vector!(f = mps(4.0), r = mps(5.0), d = mps(6.0); in TestFrd);
let sum = v1 + v2;
assert_eq!(sum.to_cartesian(), [mps(5.0), mps(7.0), mps(9.0)]);
let diff = v2 - v1;
assert_eq!(diff.to_cartesian(), [mps(3.0), mps(3.0), mps(3.0)]);
let neg = -v1;
assert_eq!(neg.to_cartesian(), [mps(-1.0), mps(-2.0), mps(-3.0)]);
let scaled = v1 * 2.0;
assert_eq!(scaled.to_cartesian(), [mps(2.0), mps(4.0), mps(6.0)]);
}
#[test]
fn zero_vector_works() {
let v = Vector::<TestFrd, N1>::zero();
assert_eq!(v.to_cartesian(), [mps(0.0), mps(0.0), mps(0.0)]);
}
#[test]
fn lerp_works() {
let v1 = vector!(f = mps(0.0), r = mps(0.0), d = mps(0.0); in TestFrd);
let v2 = vector!(f = mps(10.0), r = mps(20.0), d = mps(30.0); in TestFrd);
let mid = v1.lerp(&v2, 0.5);
assert_eq!(mid.to_cartesian(), [mps(5.0), mps(10.0), mps(15.0)]);
}
#[test]
fn with_same_components_in_works() {
let v_frd = vector!(f = mps(1.0), r = mps(2.0), d = mps(3.0); in TestFrd);
let v_ned: Vector<TestNed, N1> = v_frd.with_same_components_in();
assert_eq!(v_ned.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn cast_equivalent_coordinate_systems() {
let v_frd = vector!(f = mps(1.0), r = mps(2.0), d = mps(3.0); in TestFrd);
let v_frd2: Vector<TestFrd2, N1> = v_frd.cast();
assert_eq!(v_frd2.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn ned_accessors_work() {
let v = vector!(n = mps(1.0), e = mps(2.0), d = mps(3.0); in TestNed);
assert_eq!(v.ned_north(), mps(1.0));
assert_eq!(v.ned_east(), mps(2.0));
assert_eq!(v.ned_down(), mps(3.0));
}
#[test]
fn enu_accessors_work() {
let v = vector!(e = mps(1.0), n = mps(2.0), u = mps(3.0); in TestEnu);
assert_eq!(v.enu_east(), mps(1.0));
assert_eq!(v.enu_north(), mps(2.0));
assert_eq!(v.enu_up(), mps(3.0));
}
#[test]
fn xyz_accessors_work() {
let v = vector!(x = mps(1.0), y = mps(2.0), z = mps(3.0); in TestXyz);
assert_eq!(v.x(), mps(1.0));
assert_eq!(v.y(), mps(2.0));
assert_eq!(v.z(), mps(3.0));
}
}
mod acceleration_vectors {
use super::*;
#[test]
fn constructor_from_cartesian_works() {
#[allow(deprecated)]
let v = Vector::<TestFrd, N2>::from_cartesian(mps2(1.0), mps2(2.0), mps2(3.0));
assert_eq!(v.to_cartesian(), [mps2(1.0), mps2(2.0), mps2(3.0)]);
}
#[test]
fn build_with_components_works() {
use crate::systems::FrdComponents;
let v = Vector::<TestFrd, N2>::build(FrdComponents {
front: mps2(1.0),
right: mps2(2.0),
down: mps2(3.0),
});
let cartesian = v.to_cartesian();
assert_eq!(cartesian, [mps2(1.0), mps2(2.0), mps2(3.0)]);
}
#[test]
fn builder_pattern_works() {
let v = Vector::<TestFrd, N2>::builder()
.frd_front(mps2(1.0))
.frd_right(mps2(2.0))
.frd_down(mps2(3.0))
.build();
assert_eq!(v.frd_front(), mps2(1.0));
assert_eq!(v.frd_right(), mps2(2.0));
assert_eq!(v.frd_down(), mps2(3.0));
}
#[test]
fn magnitude_works() {
let v = vector!(f = mps2(3.0), r = mps2(4.0), d = mps2(0.0); in TestFrd);
assert_eq!(v.magnitude(), mps2(5.0));
}
#[test]
fn from_spherical_works() {
let v = Vector::<TestFrd, N2>::from_spherical(
mps2(10.0),
Angle::new::<degree>(90.0), Angle::new::<degree>(0.0), );
let cartesian = v.to_cartesian();
assert_abs_diff_eq!(
cartesian[0].get::<meter_per_second_squared>(),
10.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(
cartesian[1].get::<meter_per_second_squared>(),
0.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(
cartesian[2].get::<meter_per_second_squared>(),
0.0,
epsilon = 1e-10
);
}
#[test]
fn axis_vectors_work() {
let front = Vector::<TestFrd, N2>::frd_front_axis();
let right = Vector::<TestFrd, N2>::frd_right_axis();
let down = Vector::<TestFrd, N2>::frd_down_axis();
let front_cart = front.to_cartesian();
let right_cart = right.to_cartesian();
let down_cart = down.to_cartesian();
assert_eq!(front_cart, [mps2(1.0), mps2(0.0), mps2(0.0)]);
assert_eq!(right_cart, [mps2(0.0), mps2(1.0), mps2(0.0)]);
assert_eq!(down_cart, [mps2(0.0), mps2(0.0), mps2(1.0)]);
}
#[test]
fn arithmetic_operations_work() {
let v1 = vector!(f = mps2(1.0), r = mps2(2.0), d = mps2(3.0); in TestFrd);
let v2 = vector!(f = mps2(4.0), r = mps2(5.0), d = mps2(6.0); in TestFrd);
let sum = v1 + v2;
assert_eq!(sum.to_cartesian(), [mps2(5.0), mps2(7.0), mps2(9.0)]);
let diff = v2 - v1;
assert_eq!(diff.to_cartesian(), [mps2(3.0), mps2(3.0), mps2(3.0)]);
let neg = -v1;
assert_eq!(neg.to_cartesian(), [mps2(-1.0), mps2(-2.0), mps2(-3.0)]);
let scaled = v1 * 2.0;
assert_eq!(scaled.to_cartesian(), [mps2(2.0), mps2(4.0), mps2(6.0)]);
}
#[test]
fn zero_vector_works() {
let v = Vector::<TestFrd, N2>::zero();
assert_eq!(v.to_cartesian(), [mps2(0.0), mps2(0.0), mps2(0.0)]);
}
#[test]
fn lerp_works() {
let v1 = vector!(f = mps2(0.0), r = mps2(0.0), d = mps2(0.0); in TestFrd);
let v2 = vector!(f = mps2(10.0), r = mps2(20.0), d = mps2(30.0); in TestFrd);
let mid = v1.lerp(&v2, 0.5);
assert_eq!(mid.to_cartesian(), [mps2(5.0), mps2(10.0), mps2(15.0)]);
}
#[test]
fn with_same_components_in_works() {
let v_frd = vector!(f = mps2(1.0), r = mps2(2.0), d = mps2(3.0); in TestFrd);
let v_ned: Vector<TestNed, N2> = v_frd.with_same_components_in();
assert_eq!(v_ned.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn cast_equivalent_coordinate_systems() {
let v_frd = vector!(f = mps2(1.0), r = mps2(2.0), d = mps2(3.0); in TestFrd);
let v_frd2: Vector<TestFrd2, N2> = v_frd.cast();
assert_eq!(v_frd2.to_cartesian(), v_frd.to_cartesian());
}
#[test]
fn ned_accessors_work() {
let v = vector!(n = mps2(1.0), e = mps2(2.0), d = mps2(3.0); in TestNed);
assert_eq!(v.ned_north(), mps2(1.0));
assert_eq!(v.ned_east(), mps2(2.0));
assert_eq!(v.ned_down(), mps2(3.0));
}
#[test]
fn enu_accessors_work() {
let v = vector!(e = mps2(1.0), n = mps2(2.0), u = mps2(3.0); in TestEnu);
assert_eq!(v.enu_east(), mps2(1.0));
assert_eq!(v.enu_north(), mps2(2.0));
assert_eq!(v.enu_up(), mps2(3.0));
}
#[test]
fn xyz_accessors_work() {
let v = vector!(x = mps2(1.0), y = mps2(2.0), z = mps2(3.0); in TestXyz);
assert_eq!(v.x(), mps2(1.0));
assert_eq!(v.y(), mps2(2.0));
assert_eq!(v.z(), mps2(3.0));
}
}
mod coordinate_vector_conversions {
use super::*;
#[test]
fn coordinate_to_vector_conversion_works() {
let coord = coordinate!(f = m(1.0), r = m(2.0), d = m(3.0); in TestFrd);
let vec: Vector<TestFrd> = coord.into();
assert_eq!(vec.to_cartesian(), [m(1.0), m(2.0), m(3.0)]);
}
#[test]
fn multiply_acceleration_by_time_gives_velocity() {
let acc = vector!(f = mps2(1.0), r = mps2(2.0), d = mps2(3.0); in TestFrd);
let vel: Vector<TestFrd, N1> =
acc * uom::si::f64::Time::new::<uom::si::time::second>(2.0);
assert_eq!(vel.to_cartesian(), [mps(2.0), mps(4.0), mps(6.0)]);
}
#[test]
fn multiply_velocity_by_time_gives_length() {
let vel = vector!(f = mps(1.0), r = mps(2.0), d = mps(3.0); in TestFrd);
let len: Vector<TestFrd, Z0> =
vel * uom::si::f64::Time::new::<uom::si::time::second>(2.0);
assert_eq!(len.to_cartesian(), [m(2.0), m(4.0), m(6.0)]);
}
}
}