(Floating) Points and Vectors

Предадени решения

Краен срок:
26.11.2018 17:00
Точки:
20

Срокът за предаване на решения е отминал

В това домашно ще дефинираме оператори и ще смятаме с floating point числа. Прочетете докрай за базов тест и допълнителни бележки.

Точки и вектори

Искаме да дефинирате малко геометрични примитиви и да ги обогатите със смислени операции. Първите две ще са точки и вектори:

#[derive(Debug, Clone, Copy)]
pub struct Point {
    // Каквито полета ви трябват
}

impl Point {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        unimplemented!()
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Vector {
    // Каквито полета ви трябват
}

impl Vector {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        unimplemented!()
    }
}

(Забележете, че derive-ваме Copy. Какво би се променило ако го махнете? Как ще изглежда употребата на оператори?)

Векторите ще трябва да се сравняват с други вектори, за може тестовете изобщо да се компилират.

Ще искаме да дефинирате алгебрични операции върху точките и векторите. Ще се наложи да потърсите в документацията как трябва да се имплементират trait-овете от стандартната библиотека (в std::ops), и да напишете имплементации за вашите типове. Внимавайте кода ви да се компилира с базовия тест! Ако не се компилира, няма да можете да пуснете решение!

Операции

let v1 = Vector::new(1.0, 1.0, 1.0);
let v2 = Vector::new(2.0, 2.0, 2.0);

let p1 = Point::new(1.0, 1.0, 1.0);
let p2 = Point::new(2.0, 2.0, 2.0);

Сравнение (PartialEq):

assert!(v1 == v1);
assert!(p1 == p1);

assert!(v1 != v2);
assert!(p1 != p2);

Важно е да имплементирате сравнението смислено за floating-point числа. Ако съберете 0.1 и 0.2, вероятно ще получите 0.30000000000000004. Може да "оправите" проблема при това събиране, като сравните (0.1 + 0.2).abs() - 0.3 < std::f64::EPSILON.

Събиране на точка с вектор дава нова точка, отместена с този вектор (Аdd):

assert_eq!(p1 + v1, p2);

Изваждане на точка от друга точка е обратната операция, дава вектора, който е отместването между двете точки (Sub):

assert_eq!(p2 - p1, v1);

Събиране на вектор с вектор дава нов вектор, който е сумата на координатите (Add):

assert_eq!(v1 + v1, v2);

Изваждане на вектор от вектор и отрицание (Neg) не изискваме (няма да тестваме), но може да ги имплементирате, ако искате.

Умножение на число с вектор (Mul):

assert_eq!(2.0 * v1, v2);

Забележете, че операцията не е симетрична, и искаме да имплементирате конкретно тази посока, число по вектор. Свободни сте да имплементирате и двете посоки, ако искате.

Няма да изискваме деление, защото може да се имплементира лесно с умножение по реципрочно. Но може да го имплементирате, ако искате.

Скаларно умножение на вектори, "dot product". Връща f64 (Mul):

assert_eq!(v1 * v2, 6.0);

Векторно умножение, "cross product". Връща нов вектор (BitXor):

assert_eq!(v1 ^ v2, Vector::new(0.0, 0.0, 0.0));

Формула за координатите може да се намери в уикипедия.

Линия

Всичко това ще използваме, за да конструираме една линия (безкрайна права) в тримерното пространство и да определим дали две линии са всъщност една и съща.

Започваме от дефиницията на линия и няколко метода за конструиране:

#[derive(Debug)]
pub struct Line {
    // Каквито полета ви трябват
}

impl Line {
    /// Конструиране на линия през две точки, които минават през нея. Две различни точки са
    /// достатъчни, за да дефинират еднозначно линия.
    ///
    /// Можете да получите точка и вектор, като извадите едната от другата точка.
    ///
    /// Ако точките са една и съща, очакваме да върнете None.
    ///
    pub fn from_pp(p1: Point, p2: Point) -> Option<Self> {
        unimplemented!()
    }

    /// Конструиране на линия през точка за начало, и вектор, който определя посоката. Стига
    /// вектора да е ненулев, това е достатъчно, за да дефинира еднозначно линия.
    ///
    /// Може да получите две точки, като съберете дадената с вектора.
    ///
    /// Ако вектора е нулев, очакваме да върнете None.
    ///
    pub fn from_pv(p: Point, v: Vector) -> Option<Self> {
        unimplemented!()
    }
}

Искаме да дефинирате и разстояние на една точка от линия:

impl Line {
    pub fn distance(&self, target: Point) -> f64 {
        unimplemented!()
    }
}

Можете да намерите методи за смятане на разстоянието в уикипедия (На мен най-много ми харесва описания в "Another vector formulation").

Равенство на линии

Искаме да дефинирате и равенство на две линии (PartialEq). Забележете, че равенство не означава подадените параметри да са равни:

assert_eq!(
    Line::from_pv(Point::new(0.0, 0.0, 0.0), Vector::new(1.0, 1.0, 1.0)),
    Line::from_pv(Point::new(0.0, 0.0, 0.0), Vector::new(2.0, 2.0, 2.0))
);

Вектора (1, 1, 1) и вектора (2, 2, 2) имат една и съща посока, което значи, че линия с начало (0, 0, 0) и вектор който и да е от двата е една и съща линия. Същото би се случило, ако векторите бяха противоположни, примерно (1, 1, 1) и (-1, -1, -1) -- и двата лежат на една линия.

Същото се отнася за точки. Две точки дефинират линия, но не са уникални.

Как тогава да определите дали две линии са равни, т.е. дали съвпадат? Ако линиите са дефинирани с две различни точки всяка, може да вземете тези две точки на едната линия и да проверите дали разстоянието до другата линия и на двете е 0. Това означава, че и двете точки, които дефинират втората линия лежат и на първата, което значи, че са "равни" по нашата дефиниция.

Допълнителни бележки

Не е задължително да използвате точно структурата, която сме ви показали, стига да работи по същия начин. Примерно, точката Point може и да се дефинира така:

pub struct Point(...);

Стига асоциираната функция Point::new да e дефинирана както очакваме, вътрешната структура няма значение.

Не забравяйте за сравнението на floating point числа! Очакваме да ползвате std::f64::EPSILON за всякакви сравнения между float-ове.

Не е нужно да правите проверки за "ненормални" floating-point числа, т.е. inf и nan. Първото идва от x/0.0, второто от 0.0/0.0. Не за друго, просто ще усложни ненужно нещата. Можете да имплементирате поддържка за тях, ако искате (Как се сравнява безкрайност с безкрайност? Не се сравнява :))

Няколко геометрични уточнения:

  • "norm" на един вектор е "дължината" му и е равна на корен квадратен от сумата на квадратите на координатите: Euclidean norm.
  • нормализиран вектор или "unit vector" е такъв с дължина 1. За да нормализираме вектор, разделяме всеки от координатите му на дължината на вектора.

Примерния тест се намира тук. Погрижете се да се компилира!