Стаття
Геометрія · ⏱ ≈ 13 хв читання · Останнє оновлення: 5 липня 2026 р.

Кватерніони без страху

Вільям Роуен Гамільтон вирізьбив i² = j² = k² = ijk = −1 на дублінському мосту 1843 року. Кватерніони виявилися найелегантнішим способом представляти 3D-обертання: без карданного замка, з плавною сферичною інтерполяцією та лише 4 числами. Кожен ігровий рушій використовує їх усередині.

1. Від комплексних чисел до ℍ

Комплексні числа ℂ = {a + bi} розширюють ℝ однією уявною одиницею, що задовольняє i² = −1. Вони представляють 2D-обертання: множення на e^{iθ} = cos θ + i sin θ обертає точку на θ.

Прозріння Гамільтона: щоб представляти 3D-обертання, потрібні три уявні одиниці i, j, k, що задовольняють:

i² = j² = k² = −1 ij = k, jk = i, ki = j ji = −k, kj = −i, ik = −j (некомутативно!) Кватерніон: q = w + xi + yj + zk (w, x, y, z ∈ ℝ) q = (w, v), де v = (x,y,z) — векторна частина

Кватерніони ℍ утворюють алгебру з діленням — кожен ненульовий кватерніон має мультиплікативний обернений. Це остання асоціативна алгебра з діленням (за теоремою Фробеніуса: лише ℝ, ℂ, ℍ підходять).

2. Алгебра кватерніонів

Додавання: p + q = (w₁+w₂, x₁+x₂, y₁+y₂, z₁+z₂) Добуток Гамільтона: pq = (w₁w₂ − v₁·v₂, w₁v₂ + w₂v₁ + v₁×v₂) Спряжений: q* = (w, −x, −y, −z) Норма: |q| = √(w²+x²+y²+z²) Обернений: q⁻¹ = q* / |q|² Одиничний кватерніон: |q| = 1 → q⁻¹ = q*

Некомутативність

pq ≠ qp загалом. Це відображає той факт, що 3D-обертання не комутують: спершу обернути X, потім Y — відрізняється від спершу Y, потім X.

Антиподальна тотожність

q та −q представляють те саме обертання. Подвійне накриття S³ → SO(3) означає, що кожному обертанню відповідають два одиничні кватерніони.

Чистий кватерніон

Кватерніон з w=0. Добутки чистих кватерніонів містять скалярний та векторний добутки 3D-векторів — причина, з якої Гамільтон їх винайшов.

Експоненційне відображення

exp(θ/2 · n̂) = cos(θ/2) + n̂ sin(θ/2). Аналогічно формулі Ейлера, це породжує всі одиничні кватерніони з осі-кута (n̂, θ).

3. Обертання через кватерніони

Щоб обернути 3D-вектор v на кут θ навколо одиничної осі , формуємо одиничний кватерніон:

q = cos(θ/2) + n̂ · sin(θ/2) = (cos(θ/2), nx·sin(θ/2), ny·sin(θ/2), nz·sin(θ/2)) «Сендвіч»-обертання: v' = q · (0,v) · q* (вкладаємо v як чистий кватерніон, потім «сендвіч») v' теж є чистим кватерніоном — його векторна частина — це обернений v. Композиція обертань: спершу q₁, потім q₂ → застосувати q₂q₁ (справа наліво) v' = (q₂q₁) · v · (q₂q₁)*

Кут/2 з’являється тому, що одиничні кватерніони живуть на S³ (3-сфера в ℝ⁴), яка подвійно накриває SO(3). Повне обертання на 2π відповідає проходженню q половини шляху навколо S³.

4. Карданний замок у кутах Ейлера

Кути Ейлера представляють обертання як три послідовні обертання навколо координатних осей (наприклад, конвенція ZYX: рискання, тангаж, крен). Середнє обертання на 90° вирівнює дві осі, зменшуючи ефективну кількість ступенів свободи з 3 до 2 — карданний замок:

R = Rz(ψ) · Ry(θ) · Rx(φ) Коли θ = π/2: Ry(π/2) відображає вісь x на вісь z Rz(ψ) · Rx(φ) зводяться до обертань у ТІЙ САМІЙ площині → рискання й крен стають нерозрізненними → втрачений ступінь свободи На практиці: підняття носа літака на 90° спричиняє нестабільність автопілота Суглоб анімації в екстремальних позах смикається / гортається Чисельно: якобіан втрачає ранг → інтегрування розбігається

Кватерніони не мають карданного замка, бо параметризують SO(3) глобально (за винятком антиподального ототожнення подвійного накриття) без сингулярностей. Саме тому системи керування польотом, керування орієнтацією космічних апаратів та 3D-рушії всередині використовують кватерніони.

5. Кватерніон ↔ матриця ↔ кути Ейлера

Одиничний кватерніон q = (w, x, y, z) → матриця обертання 3×3: R = | 1−2(y²+z²) 2(xy−wz) 2(xz+wy) | | 2(xy+wz) 1−2(x²+z²) 2(yz−wx) | | 2(xz−wy) 2(yz+wx) 1−2(x²+y²) | Матриця → кватерніон (метод Шепперда): t = trace(R) = 3 − 4(x²+y²+z²) w = √(1+t)/2, потім x,y,z з позадіагональних елементів Кватерніон → кути Ейлера (ZYX): рискання ψ = atan2(2(wz+xy), 1−2(y²+z²)) тангаж θ = arcsin(clamp(2(wy−zx), −1, 1)) крен φ = atan2(2(wx+yz), 1−2(x²+y²))

6. SLERP — сферична лінійна інтерполяція

Лінійна інтерполяція (LERP) обертань не лишається на одиничній сфері. SLERP інтерполює вздовж дуги великого кола — геодезичної — між двома одиничними кватерніонами:

SLERP(q₀, q₁, t) = q₀ · (q₀⁻¹ · q₁)^t = sin((1−t)Ω)/sinΩ · q₀ + sin(tΩ)/sinΩ · q₁ де Ω = arccos(q₀ · q₁) (половина кута між обертаннями) Окремий випадок: якщо Ω ≈ 0, використовуйте NLERP (нормалізований LERP), щоб уникнути ділення на нуль: q(t) = normalise((1−t)·q₀ + t·q₁) NLERP трохи швидший і майже ідентичний для малих кутів.

SLERP дає рух зі сталою кутовою швидкістю між двома орієнтаціями — ключове для плавного панорамування камери, змішування анімації персонажів та маневрів орієнтації космічних апаратів.

7. Клас Quaternion на JavaScript

// Мінімальна бібліотека кватерніонів для 3D-обертань
class Quat {
  constructor(w = 1, x = 0, y = 0, z = 0) {
    this.w = w; this.x = x; this.y = y; this.z = z;
  }
  static fromAxisAngle(ax, ay, az, angle) {
    const s = Math.sin(angle / 2);
    return new Quat(Math.cos(angle / 2), ax*s, ay*s, az*s);
  }
  mul(q) {
    return new Quat(
      this.w*q.w - this.x*q.x - this.y*q.y - this.z*q.z,
      this.w*q.x + this.x*q.w + this.y*q.z - this.z*q.y,
      this.w*q.y - this.x*q.z + this.y*q.w + this.z*q.x,
      this.w*q.z + this.x*q.y - this.y*q.x + this.z*q.w
    );
  }
  conjugate() { return new Quat(this.w, -this.x, -this.y, -this.z); }
  norm() { return Math.sqrt(this.w**2+this.x**2+this.y**2+this.z**2); }
  normalise() { const n=this.norm(); return new Quat(this.w/n,this.x/n,this.y/n,this.z/n); }

  // Обертаємо вектор v = {x,y,z} «сендвіч»-добутком q·v·q*
  rotateVec(v) {
    const p = new Quat(0, v.x, v.y, v.z);
    const r = this.mul(p).mul(this.conjugate());
    return {x: r.x, y: r.y, z: r.z};
  }

  // SLERP між this та q при параметрі t ∈ [0,1]
  slerp(q, t) {
    let dot = this.w*q.w + this.x*q.x + this.y*q.y + this.z*q.z;
    // Гарантуємо найкоротший шлях
    if (dot < 0) { q = new Quat(-q.w,-q.x,-q.y,-q.z); dot = -dot; }
    if (dot > 0.9995) {
      // Використовуємо NLERP для майже однакових кватерніонів
      return new Quat(
        this.w + t*(q.w-this.w), this.x + t*(q.x-this.x),
        this.y + t*(q.y-this.y), this.z + t*(q.z-this.z)
      ).normalise();
    }
    const theta = Math.acos(dot);
    const sinT  = Math.sin(theta);
    const s0 = Math.sin((1-t)*theta)/sinT;
    const s1 = Math.sin(t*theta)/sinT;
    return new Quat(s0*this.w+s1*q.w, s0*this.x+s1*q.x,
                     s0*this.y+s1*q.y, s0*this.z+s1*q.z);
  }

  toMatrix4() {
    const {w,x,y,z} = this;
    return [/* стовпцевий 4×4 для WebGL */
      1-2*(y*y+z*z), 2*(x*y+w*z),   2*(x*z-w*y),   0,
      2*(x*y-w*z),   1-2*(x*x+z*z), 2*(y*z+w*x),   0,
      2*(x*z+w*y),   2*(y*z-w*x),   1-2*(x*x+y*y), 0,
      0,               0,               0,               1
    ];
  }
}

// Приклад: обернути (1,0,0) на 90° навколо осі Y → має дати (0,0,−1)
const q = Quat.fromAxisAngle(0, 1, 0, Math.PI / 2);
const v = q.rotateVec({x:1, y:0, z:0});
console.log(v); // → {x: ≈0, y: 0, z: ≈−1}

8. Застосування

Порада: Three.js надає THREE.Quaternion з setFromAxisAngle, multiply, slerp та toArray — ті самі операції, що й у нашому класі вище, оптимізовані для WebGL.