use super::{Direction, Displacement, Position};
use crate::centers::ReferenceCenter;
use crate::frames::ReferenceFrame;
use qtty::{LengthUnit, Quantity};
#[inline]
pub fn line_of_sight<C, F, U>(
observer: &Position<C, F, U>,
target: &Position<C, F, U>,
) -> Direction<F>
where
C: ReferenceCenter,
F: ReferenceFrame,
U: LengthUnit,
{
let displacement: Displacement<F, U> = target - observer;
displacement
.normalize()
.expect("line_of_sight requires distinct observer and target positions")
}
#[inline]
pub fn line_of_sight_with_distance<C, F, U>(
observer: &Position<C, F, U>,
target: &Position<C, F, U>,
) -> (Direction<F>, Quantity<U>)
where
C: ReferenceCenter,
F: ReferenceFrame,
U: LengthUnit,
{
let displacement: Displacement<F, U> = target - observer;
let distance = displacement.magnitude();
let direction = displacement
.normalize()
.expect("line_of_sight requires distinct observer and target positions");
(direction, distance)
}
#[inline]
pub fn try_line_of_sight<C, F, U>(
observer: &Position<C, F, U>,
target: &Position<C, F, U>,
) -> Option<Direction<F>>
where
C: ReferenceCenter,
F: ReferenceFrame,
U: LengthUnit,
{
let displacement: Displacement<F, U> = target - observer;
displacement.normalize()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DeriveReferenceCenter as ReferenceCenter, DeriveReferenceFrame as ReferenceFrame};
use qtty::Meter;
#[derive(Debug, Copy, Clone, ReferenceFrame)]
struct TestFrame;
#[derive(Debug, Copy, Clone, ReferenceCenter)]
struct TestCenter;
type TestPos = Position<TestCenter, TestFrame, Meter>;
#[test]
fn test_line_of_sight_basic() {
let observer = TestPos::new(0.0, 0.0, 0.0);
let target = TestPos::new(3.0, 4.0, 0.0);
let los = line_of_sight(&observer, &target);
assert!((los.x() - 0.6).abs() < 1e-12);
assert!((los.y() - 0.8).abs() < 1e-12);
assert!(los.z().abs() < 1e-12);
}
#[test]
fn test_line_of_sight_with_distance() {
let observer = TestPos::new(1.0, 1.0, 1.0);
let target = TestPos::new(4.0, 5.0, 1.0);
let (dir, dist) = line_of_sight_with_distance(&observer, &target);
assert!((dist.value() - 5.0).abs() < 1e-12);
assert!((dir.x() - 0.6).abs() < 1e-12);
assert!((dir.y() - 0.8).abs() < 1e-12);
}
#[test]
fn test_try_line_of_sight_same_position() {
let pos = TestPos::new(1.0, 2.0, 3.0);
assert!(try_line_of_sight(&pos, &pos).is_none());
}
#[test]
fn test_position_displacement_roundtrip() {
let a = TestPos::new(1.0, 2.0, 3.0);
let b = TestPos::new(5.0, 7.0, 11.0);
let displacement = b - a;
let result = a + displacement;
assert!((result.x().value() - b.x().value()).abs() < 1e-12);
assert!((result.y().value() - b.y().value()).abs() < 1e-12);
assert!((result.z().value() - b.z().value()).abs() < 1e-12);
}
#[test]
fn test_direction_scale_to_displacement() {
use qtty::Quantity;
let dir = Direction::<TestFrame>::new(1.0, 0.0, 0.0);
let disp: Displacement<TestFrame, Meter> = dir * Quantity::new(3.0);
assert!((disp.x().value() - 3.0).abs() < 1e-12);
assert!(disp.y().value().abs() < 1e-12);
}
#[test]
fn test_displacement_normalize_to_direction() {
let disp = Displacement::<TestFrame, Meter>::new(3.0, 4.0, 0.0);
let dir = disp.normalize().expect("non-zero displacement");
assert!((dir.x() - 0.6).abs() < 1e-12);
assert!((dir.y() - 0.8).abs() < 1e-12);
assert!(dir.z().abs() < 1e-12);
}
}