Last active
July 3, 2026 16:16
-
-
Save potat-dev/874ca517191759659bec2664d22d4775 to your computer and use it in GitHub Desktop.
Simple library to calculate Quaternion rotations in Python. No external libraries required!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math | |
| from typing import Self | |
| from dataclasses import dataclass | |
| from math import cos, sin, sqrt | |
| @dataclass(slots=True) | |
| class Vector: | |
| x: float | |
| y: float | |
| z: float | |
| @property | |
| def length(self) -> float: | |
| return sqrt(self.x**2 + self.y**2 + self.z**2) | |
| @property | |
| def normal(self) -> Self: | |
| if (L := self.length) == 0: | |
| raise ValueError("Cannot normalize vector with lenght 0") | |
| return Vector(self.x / L, self.y / L, self.z / L) | |
| def __add__(self, other: Self) -> Self: | |
| return Vector(self.x + other.x, self.y + other.y, self.z + other.z) | |
| def __mul__(self, scalar: float) -> Self: | |
| return Vector(self.x * scalar, self.y * scalar, self.z * scalar) | |
| def __rmul__(self, scalar: float) -> Self: | |
| return self.__mul__(scalar) | |
| def dot(self, other: Self) -> float: | |
| return self.x * other.x + self.y * other.y + self.z * other.z | |
| def cross(self, other: Self) -> Self: | |
| return Vector( | |
| self.y * other.z - self.z * other.y, | |
| self.z * other.x - self.x * other.z, | |
| self.x * other.y - self.y * other.x, | |
| ) | |
| @dataclass(slots=True) | |
| class Quaternion: | |
| w: float | |
| x: float | |
| y: float | |
| z: float | |
| @property | |
| def vector(self) -> Vector: | |
| return Vector(self.x, self.y, self.z) | |
| def __mul__(self, other: Self) -> Self: | |
| v1, v2 = self.vector, other.vector | |
| w = self.w * other.w - v1.dot(v2) | |
| v = (self.w * v2) + (other.w * v1) + v1.cross(v2) | |
| return Quaternion(w, v.x, v.y, v.z) | |
| @property | |
| def normal(self) -> Self: | |
| if (L := self.length) == 0: | |
| raise ValueError("Cannot normalize quaternion with lenght 0") | |
| return Quaternion(self.w / L, self.x / L, self.y / L, self.z / L) | |
| @classmethod | |
| def from_vector_angle(cls, vec: Vector, angle: float) -> Self: | |
| """ | |
| Constructs a rotational quaternion from vector and angle. | |
| Angle should be in degrees. Vector can be lenght != 1. | |
| """ | |
| vec_norm = vec.normal | |
| half_angle = math.radians(angle) / 2.0 | |
| w, s = cos(half_angle), sin(half_angle) | |
| return cls(w, vec_norm.x * s, vec_norm.y * s, vec_norm.z * s) | |
| @classmethod | |
| def from_vector(cls, vec: Vector) -> Self: | |
| return cls(0, vec.x, vec.y, vec.z) | |
| @property | |
| def conjugate(self) -> Self: | |
| return Quaternion(self.w, -self.x, -self.y, -self.z) | |
| @property | |
| def length(self) -> float: | |
| return sqrt(self.w**2 + self.x**2 + self.y**2 + self.z**2) | |
| def rotated_by(self, other: Self) -> Self: | |
| return other * self * other.conjugate |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment