Packages

Pure Gleam quaternion math library for 3D rotations

Current section

Files

Jump to
quaterni src quaternion.erl
Raw

src/quaternion.erl

-module(quaternion).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/quaternion.gleam").
-export([from_axis_angle/2, from_euler/1, to_euler/1, multiply/2, conjugate/1, dot/2, rotate/2, angle/1, axis/1, loosely_equals/3, normalize/1, from_to_rotation/2, inverse/1, spherical_linear_interpolation/3, linear_interpolation/3, look_at/3]).
-export_type([quaternion/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Pure Gleam quaternion math library for 3D rotations.\n"
"\n"
" Quaternions are a mathematical representation of rotations in 3D space that:\n"
" - Avoid gimbal lock\n"
" - Provide smooth interpolation (slerp)\n"
" - Are more compact than rotation matrices\n"
" - Compose efficiently\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import q\n"
" import vec/vec3\n"
"\n"
" // Create quaternion from axis-angle\n"
" let rotation = q.from_axis_angle(vec3.Vec3(0.0, 1.0, 0.0), 1.57)\n"
"\n"
" // Or from Euler angles\n"
" let rotation = q.from_euler(vec3.Vec3(0.0, 1.57, 0.0))\n"
"\n"
" // Rotate a vector\n"
" let rotated = q.rotate(rotation, vec3.Vec3(1.0, 0.0, 0.0))\n"
"\n"
" // Interpolate between rotations\n"
" let halfway = q.slerp(from: rot1, to: rot2, t: 0.5)\n"
" ```\n"
).
-type quaternion() :: {quaternion, float(), float(), float(), float()}.
-file("src/quaternion.gleam", 61).
?DOC(
" Create a quaternion from axis-angle representation.\n"
"\n"
" ## Parameters\n"
" - `axis`: The rotation axis\n"
" - `angle`: The rotation angle in radians\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // 90 degree rotation around Y axis\n"
" let rotation = q.from_axis_angle(vec3.Vec3(0.0, 1.0, 0.0), 1.57)\n"
" ```\n"
).
-spec from_axis_angle(vec@vec3:vec3(float()), float()) -> quaternion().
from_axis_angle(Axis, Angle) ->
Axis@1 = vec@vec3f:normalize(Axis),
Half_angle = Angle / 2.0,
S = gleam_community@maths:sin(Half_angle),
{quaternion,
erlang:element(2, Axis@1) * S,
erlang:element(3, Axis@1) * S,
erlang:element(4, Axis@1) * S,
gleam_community@maths:cos(Half_angle)}.
-file("src/quaternion.gleam", 81).
?DOC(
" Convert Euler angles (radians) to quaternion using XYZ rotation order.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // Rotate 90 degrees around Y axis\n"
" let rotation = q.from_euler(vec3.Vec3(0.0, 1.57, 0.0))\n"
" ```\n"
).
-spec from_euler(vec@vec3:vec3(float())) -> quaternion().
from_euler(Euler) ->
C1 = gleam_community@maths:cos(erlang:element(2, Euler) / 2.0),
C2 = gleam_community@maths:cos(erlang:element(3, Euler) / 2.0),
C3 = gleam_community@maths:cos(erlang:element(4, Euler) / 2.0),
S1 = gleam_community@maths:sin(erlang:element(2, Euler) / 2.0),
S2 = gleam_community@maths:sin(erlang:element(3, Euler) / 2.0),
S3 = gleam_community@maths:sin(erlang:element(4, Euler) / 2.0),
{quaternion,
((S1 * C2) * C3) + ((C1 * S2) * S3),
((C1 * S2) * C3) - ((S1 * C2) * S3),
((C1 * C2) * S3) + ((S1 * S2) * C3),
((C1 * C2) * C3) - ((S1 * S2) * S3)}.
-file("src/quaternion.gleam", 101).
?DOC(
" Convert quaternion to Euler angles (radians) using XYZ rotation order.\n"
"\n"
" Returns Vec3(roll, pitch, yaw).\n"
).
-spec to_euler(quaternion()) -> vec@vec3:vec3(float()).
to_euler(Quat) ->
Sinr_cosp = 2.0 * ((erlang:element(5, Quat) * erlang:element(2, Quat)) + (erlang:element(
3,
Quat
)
* erlang:element(4, Quat))),
Cosr_cosp = 1.0 - (2.0 * ((erlang:element(2, Quat) * erlang:element(2, Quat))
+ (erlang:element(3, Quat) * erlang:element(3, Quat)))),
Roll = gleam_community@maths:atan2(Sinr_cosp, Cosr_cosp),
Sinp = 2.0 * ((erlang:element(5, Quat) * erlang:element(3, Quat)) - (erlang:element(
4,
Quat
)
* erlang:element(2, Quat))),
Pitch = case Sinp >= 1.0 of
true ->
gleam_community@maths:pi() / 2.0;
false ->
case Sinp =< -1.0 of
true ->
+0.0 - (gleam_community@maths:pi() / 2.0);
false ->
_pipe = gleam_community@maths:asin(Sinp),
gleam@result:unwrap(_pipe, +0.0)
end
end,
Siny_cosp = 2.0 * ((erlang:element(5, Quat) * erlang:element(4, Quat)) + (erlang:element(
2,
Quat
)
* erlang:element(3, Quat))),
Cosy_cosp = 1.0 - (2.0 * ((erlang:element(3, Quat) * erlang:element(3, Quat))
+ (erlang:element(4, Quat) * erlang:element(4, Quat)))),
Yaw = gleam_community@maths:atan2(Siny_cosp, Cosy_cosp),
{vec3, Roll, Pitch, Yaw}.
-file("src/quaternion.gleam", 167).
?DOC(
" Multiply two quaternions (q1 * q2).\n"
"\n"
" Represents the combined rotation of applying q1 then q2.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let rotate_y = q.from_axis_angle(vec3.Vec3(0.0, 1.0, 0.0), 1.57)\n"
" let rotate_x = q.from_axis_angle(vec3.Vec3(1.0, 0.0, 0.0), 0.5)\n"
" let combined = q.multiply(rotate_y, rotate_x)\n"
" ```\n"
).
-spec multiply(quaternion(), quaternion()) -> quaternion().
multiply(Q1, Q2) ->
{quaternion,
(((erlang:element(5, Q1) * erlang:element(2, Q2)) + (erlang:element(
2,
Q1
)
* erlang:element(5, Q2)))
+ (erlang:element(3, Q1) * erlang:element(4, Q2)))
- (erlang:element(4, Q1) * erlang:element(3, Q2)),
(((erlang:element(5, Q1) * erlang:element(3, Q2)) - (erlang:element(
2,
Q1
)
* erlang:element(4, Q2)))
+ (erlang:element(3, Q1) * erlang:element(5, Q2)))
+ (erlang:element(4, Q1) * erlang:element(2, Q2)),
(((erlang:element(5, Q1) * erlang:element(4, Q2)) + (erlang:element(
2,
Q1
)
* erlang:element(3, Q2)))
- (erlang:element(3, Q1) * erlang:element(2, Q2)))
+ (erlang:element(4, Q1) * erlang:element(5, Q2)),
(((erlang:element(5, Q1) * erlang:element(5, Q2)) - (erlang:element(
2,
Q1
)
* erlang:element(2, Q2)))
- (erlang:element(3, Q1) * erlang:element(3, Q2)))
- (erlang:element(4, Q1) * erlang:element(4, Q2))}.
-file("src/quaternion.gleam", 203).
?DOC(
" Compute the conjugate of a quaternion.\n"
"\n"
" The conjugate represents the inverse rotation.\n"
).
-spec conjugate(quaternion()) -> quaternion().
conjugate(Quat) ->
{quaternion,
+0.0 - erlang:element(2, Quat),
+0.0 - erlang:element(3, Quat),
+0.0 - erlang:element(4, Quat),
erlang:element(5, Quat)}.
-file("src/quaternion.gleam", 228).
?DOC(" Compute the dot product of two quaternions.\n").
-spec dot(quaternion(), quaternion()) -> float().
dot(Q1, Q2) ->
(((erlang:element(2, Q1) * erlang:element(2, Q2)) + (erlang:element(3, Q1) * erlang:element(
3,
Q2
)))
+ (erlang:element(4, Q1) * erlang:element(4, Q2)))
+ (erlang:element(5, Q1) * erlang:element(5, Q2)).
-file("src/quaternion.gleam", 326).
?DOC(
" Rotate a vector by a quaternion.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let rotation = q.from_axis_angle(vec3.Vec3(0.0, 1.0, 0.0), 1.57)\n"
" let point = vec3.Vec3(1.0, 0.0, 0.0)\n"
" let rotated = q.rotate(rotation, point) // ~Vec3(0.0, 0.0, -1.0)\n"
" ```\n"
).
-spec rotate(quaternion(), vec@vec3:vec3(float())) -> vec@vec3:vec3(float()).
rotate(Quat, V) ->
Qx = erlang:element(2, Quat),
Qy = erlang:element(3, Quat),
Qz = erlang:element(4, Quat),
Qw = erlang:element(5, Quat),
Ix = ((Qw * erlang:element(2, V)) + (Qy * erlang:element(4, V))) - (Qz * erlang:element(
3,
V
)),
Iy = ((Qw * erlang:element(3, V)) + (Qz * erlang:element(2, V))) - (Qx * erlang:element(
4,
V
)),
Iz = ((Qw * erlang:element(4, V)) + (Qx * erlang:element(3, V))) - (Qy * erlang:element(
2,
V
)),
Iw = ((+0.0 - (Qx * erlang:element(2, V))) - (Qy * erlang:element(3, V))) - (Qz
* erlang:element(4, V)),
{vec3,
(((Ix * Qw) + (Iw * (+0.0 - Qx))) + (Iy * (+0.0 - Qz))) - (Iz * (+0.0 - Qy)),
(((Iy * Qw) + (Iw * (+0.0 - Qy))) + (Iz * (+0.0 - Qx))) - (Ix * (+0.0 - Qz)),
(((Iz * Qw) + (Iw * (+0.0 - Qz))) + (Ix * (+0.0 - Qy))) - (Iy * (+0.0 - Qx))}.
-file("src/quaternion.gleam", 371).
?DOC(" Get the rotation angle in radians.\n").
-spec angle(quaternion()) -> float().
angle(Quat) ->
2.0 * begin
_pipe = gleam_community@maths:acos(
gleam@float:clamp(erlang:element(5, Quat), -1.0, 1.0)
),
gleam@result:unwrap(_pipe, +0.0)
end.
-file("src/quaternion.gleam", 378).
?DOC(
" Get the rotation axis.\n"
"\n"
" Returns Error if the quaternion represents no rotation (identity).\n"
).
-spec axis(quaternion()) -> {ok, vec@vec3:vec3(float())} | {error, nil}.
axis(Quat) ->
S_squared = 1.0 - (erlang:element(5, Quat) * erlang:element(5, Quat)),
case S_squared < 0.0001 of
true ->
{error, nil};
false ->
S = case gleam@float:square_root(S_squared) of
{ok, Val} ->
Val;
{error, _} ->
+0.0
end,
{ok, {vec3, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:element(2, Quat) / Gleam@denominator
end, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:element(3, Quat) / Gleam@denominator@1
end, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> erlang:element(4, Quat) / Gleam@denominator@2
end}}
end.
-file("src/quaternion.gleam", 519).
?DOC(
" Check if two quaternions are approximately equal within a tolerance.\n"
"\n"
" Useful for floating-point comparisons where exact equality is problematic.\n"
" Note: Quaternions q and -q represent the same rotation, so this function\n"
" checks both orientations.\n"
"\n"
" ## Parameters\n"
" - `q1`: First quaternion\n"
" - `q2`: Second quaternion\n"
" - `epsilon`: Tolerance for comparison (typically 0.0001 to 0.001)\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let q1 = from_euler(Vec3(0.0, 1.57, 0.0))\n"
" let q2 = from_euler(Vec3(0.0, 1.57001, 0.0))\n"
" loosely_equals(q1, q2, epsilon: 0.001) // True\n"
" ```\n"
).
-spec loosely_equals(quaternion(), quaternion(), float()) -> boolean().
loosely_equals(Q1, Q2, Epsilon) ->
Same_orientation = (((gleam@float:absolute_value(
erlang:element(2, Q1) - erlang:element(2, Q2)
)
< Epsilon)
andalso (gleam@float:absolute_value(
erlang:element(3, Q1) - erlang:element(3, Q2)
)
< Epsilon))
andalso (gleam@float:absolute_value(
erlang:element(4, Q1) - erlang:element(4, Q2)
)
< Epsilon))
andalso (gleam@float:absolute_value(
erlang:element(5, Q1) - erlang:element(5, Q2)
)
< Epsilon),
Opposite_orientation = (((gleam@float:absolute_value(
erlang:element(2, Q1) + erlang:element(2, Q2)
)
< Epsilon)
andalso (gleam@float:absolute_value(
erlang:element(3, Q1) + erlang:element(3, Q2)
)
< Epsilon))
andalso (gleam@float:absolute_value(
erlang:element(4, Q1) + erlang:element(4, Q2)
)
< Epsilon))
andalso (gleam@float:absolute_value(
erlang:element(5, Q1) + erlang:element(5, Q2)
)
< Epsilon),
Same_orientation orelse Opposite_orientation.
-file("src/quaternion.gleam", 179).
?DOC(
" Normalize a quaternion to unit length.\n"
"\n"
" All rotation quaternions should be normalized.\n"
).
-spec normalize(quaternion()) -> quaternion().
normalize(Quat) ->
Mag = gleam@float:square_root(
(((erlang:element(2, Quat) * erlang:element(2, Quat)) + (erlang:element(
3,
Quat
)
* erlang:element(3, Quat)))
+ (erlang:element(4, Quat) * erlang:element(4, Quat)))
+ (erlang:element(5, Quat) * erlang:element(5, Quat))
),
case Mag of
{ok, M} when M > 0.0001 ->
{quaternion, case M of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:element(2, Quat) / Gleam@denominator
end, case M of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:element(3, Quat) / Gleam@denominator@1
end, case M of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> erlang:element(4, Quat) / Gleam@denominator@2
end, case M of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@3 -> erlang:element(5, Quat) / Gleam@denominator@3
end};
_ ->
{quaternion, +0.0, +0.0, +0.0, 1.0}
end.
-file("src/quaternion.gleam", 127).
?DOC(" Create a quaternion that rotates from one direction to another.\n").
-spec from_to_rotation(vec@vec3:vec3(float()), vec@vec3:vec3(float())) -> quaternion().
from_to_rotation(From, To) ->
From@1 = vec@vec3f:normalize(From),
To@1 = vec@vec3f:normalize(To),
Dot_val = vec@vec3f:dot(From@1, To@1),
case Dot_val > 0.999999 of
true ->
{quaternion, +0.0, +0.0, +0.0, 1.0};
false ->
case Dot_val < -0.999999 of
true ->
Axis = case gleam@float:absolute_value(
erlang:element(2, From@1)
)
< 0.99 of
true ->
vec@vec3f:normalize(
vec@vec3f:cross({vec3, 1.0, +0.0, +0.0}, From@1)
);
false ->
vec@vec3f:normalize(
vec@vec3f:cross({vec3, +0.0, 1.0, +0.0}, From@1)
)
end,
from_axis_angle(Axis, gleam_community@maths:pi());
false ->
Axis@1 = vec@vec3f:cross(From@1, To@1),
_pipe = {quaternion,
erlang:element(2, Axis@1),
erlang:element(3, Axis@1),
erlang:element(4, Axis@1),
1.0 + Dot_val},
normalize(_pipe)
end
end.
-file("src/quaternion.gleam", 210).
?DOC(
" Compute the inverse of a quaternion.\n"
"\n"
" For unit quaternions (normalized), this is equivalent to the conjugate.\n"
).
-spec inverse(quaternion()) -> quaternion().
inverse(Quat) ->
Norm_sq = (((erlang:element(2, Quat) * erlang:element(2, Quat)) + (erlang:element(
3,
Quat
)
* erlang:element(3, Quat)))
+ (erlang:element(4, Quat) * erlang:element(4, Quat)))
+ (erlang:element(5, Quat) * erlang:element(5, Quat)),
case Norm_sq > 0.0001 of
true ->
Conj = conjugate(Quat),
{quaternion, case Norm_sq of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:element(2, Conj) / Gleam@denominator
end, case Norm_sq of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:element(3, Conj) / Gleam@denominator@1
end, case Norm_sq of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> erlang:element(4, Conj) / Gleam@denominator@2
end, case Norm_sq of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@3 -> erlang:element(5, Conj) / Gleam@denominator@3
end};
false ->
{quaternion, +0.0, +0.0, +0.0, 1.0}
end.
-file("src/quaternion.gleam", 249).
?DOC(
" Spherical linear interpolation (slerp) between two quaternions.\n"
"\n"
" Provides smooth rotation interpolation without gimbal lock issues.\n"
"\n"
" ## Parameters\n"
" - `from`: Starting quaternion\n"
" - `to`: Target quaternion\n"
" - `t`: Interpolation factor (0.0 = from, 1.0 = to)\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let start = q.from_euler(vec3.Vec3(0.0, 0.0, 0.0))\n"
" let end = q.from_euler(vec3.Vec3(0.0, 1.57, 0.0))\n"
" let halfway = q.slerp(from: start, to: end, t: 0.5)\n"
" ```\n"
).
-spec spherical_linear_interpolation(quaternion(), quaternion(), float()) -> quaternion().
spherical_linear_interpolation(From, To, T) ->
Dot_prod = dot(From, To),
{To@1, Dot_prod@1} = case Dot_prod < +0.0 of
true ->
{{quaternion,
+0.0 - erlang:element(2, To),
+0.0 - erlang:element(3, To),
+0.0 - erlang:element(4, To),
+0.0 - erlang:element(5, To)},
+0.0 - Dot_prod};
false ->
{To, Dot_prod}
end,
case Dot_prod@1 > 0.9995 of
true ->
_pipe = {quaternion,
erlang:element(2, From) + ((erlang:element(2, To@1) - erlang:element(
2,
From
))
* T),
erlang:element(3, From) + ((erlang:element(3, To@1) - erlang:element(
3,
From
))
* T),
erlang:element(4, From) + ((erlang:element(4, To@1) - erlang:element(
4,
From
))
* T),
erlang:element(5, From) + ((erlang:element(5, To@1) - erlang:element(
5,
From
))
* T)},
normalize(_pipe);
false ->
Dot_clamped = gleam@float:clamp(Dot_prod@1, -1.0, 1.0),
Theta_0 = begin
_pipe@1 = gleam_community@maths:acos(Dot_clamped),
gleam@result:unwrap(_pipe@1, +0.0)
end,
Theta = Theta_0 * T,
Sin_theta = gleam_community@maths:sin(Theta),
Sin_theta_0 = gleam_community@maths:sin(Theta_0),
S1 = gleam_community@maths:cos(Theta) - (case Sin_theta_0 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Dot_clamped * Sin_theta / Gleam@denominator
end),
S2 = case Sin_theta_0 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> Sin_theta / Gleam@denominator@1
end,
{quaternion,
(erlang:element(2, From) * S1) + (erlang:element(2, To@1) * S2),
(erlang:element(3, From) * S1) + (erlang:element(3, To@1) * S2),
(erlang:element(4, From) * S1) + (erlang:element(4, To@1) * S2),
(erlang:element(5, From) * S1) + (erlang:element(5, To@1) * S2)}
end.
-file("src/quaternion.gleam", 302).
?DOC(
" Linear interpolation between two quaternions.\n"
"\n"
" Faster than slerp but doesn't maintain constant angular velocity.\n"
" Result should be normalized.\n"
).
-spec linear_interpolation(quaternion(), quaternion(), float()) -> quaternion().
linear_interpolation(From, To, T) ->
_pipe = {quaternion,
erlang:element(2, From) + ((erlang:element(2, To) - erlang:element(
2,
From
))
* T),
erlang:element(3, From) + ((erlang:element(3, To) - erlang:element(
3,
From
))
* T),
erlang:element(4, From) + ((erlang:element(4, To) - erlang:element(
4,
From
))
* T),
erlang:element(5, From) + ((erlang:element(5, To) - erlang:element(
5,
From
))
* T)},
normalize(_pipe).
-file("src/quaternion.gleam", 410).
?DOC(
" Create a quaternion that looks from one direction toward a target direction.\n"
"\n"
" Creates a rotation that orients the `forward` direction to point toward the `target` direction,\n"
" with the given `up` vector for orientation. Useful for cameras and billboards.\n"
"\n"
" ## Parameters\n"
" - `forward`: The current forward direction (usually Vec3(0.0, 0.0, -1.0) for cameras)\n"
" - `target`: The direction to look toward \n"
" - `up`: The up vector for orientation (usually Vec3(0.0, 1.0, 0.0))\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // Make camera look at target from position\n"
" let camera_pos = Vec3(10.0, 10.0, 10.0)\n"
" let target_pos = Vec3(0.0, 0.0, 0.0)\n"
" let direction = vec3f.normalize(vec3f.subtract(target_pos, camera_pos))\n"
" let quat = look_at(Vec3(0.0, 0.0, -1.0), direction, Vec3(0.0, 1.0, 0.0))\n"
" ```\n"
).
-spec look_at(
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float())
) -> quaternion().
look_at(_, Target, Up) ->
Target_norm = vec@vec3f:normalize(Target),
Up_norm = vec@vec3f:normalize(Up),
Right = vec@vec3f:normalize(vec@vec3f:cross(Up_norm, Target_norm)),
New_up = vec@vec3f:cross(Target_norm, Right),
M00 = erlang:element(2, Right),
M10 = erlang:element(3, Right),
M20 = erlang:element(4, Right),
M01 = erlang:element(2, New_up),
M11 = erlang:element(3, New_up),
M21 = erlang:element(4, New_up),
M02 = +0.0 - erlang:element(2, Target_norm),
M12 = +0.0 - erlang:element(3, Target_norm),
M22 = +0.0 - erlang:element(4, Target_norm),
Trace = (M00 + M11) + M22,
case Trace > +0.0 of
true ->
S = begin
_pipe = gleam@float:square_root(Trace + 1.0),
gleam@result:unwrap(_pipe, 1.0)
end,
W = S / 2.0,
S@1 = case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 0.5 / Gleam@denominator
end,
_pipe@1 = {quaternion,
(M21 - M12) * S@1,
(M02 - M20) * S@1,
(M10 - M01) * S@1,
W},
normalize(_pipe@1);
false ->
case (M00 > M11) andalso (M00 > M22) of
true ->
S@2 = begin
_pipe@2 = gleam@float:square_root(
((1.0 + M00) - M11) - M22
),
gleam@result:unwrap(_pipe@2, 1.0)
end,
X = S@2 / 2.0,
S@3 = case S@2 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> 0.5 / Gleam@denominator@1
end,
_pipe@3 = {quaternion,
X,
(M01 + M10) * S@3,
(M02 + M20) * S@3,
(M21 - M12) * S@3},
normalize(_pipe@3);
false ->
case M11 > M22 of
true ->
S@4 = begin
_pipe@4 = gleam@float:square_root(
((1.0 + M11) - M00) - M22
),
gleam@result:unwrap(_pipe@4, 1.0)
end,
Y = S@4 / 2.0,
S@5 = case S@4 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> 0.5 / Gleam@denominator@2
end,
_pipe@5 = {quaternion,
(M01 + M10) * S@5,
Y,
(M12 + M21) * S@5,
(M02 - M20) * S@5},
normalize(_pipe@5);
false ->
S@6 = begin
_pipe@6 = gleam@float:square_root(
((1.0 + M22) - M00) - M11
),
gleam@result:unwrap(_pipe@6, 1.0)
end,
Z = S@6 / 2.0,
S@7 = case S@6 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@3 -> 0.5 / Gleam@denominator@3
end,
_pipe@7 = {quaternion,
(M02 + M20) * S@7,
(M12 + M21) * S@7,
Z,
(M10 - M01) * S@7},
normalize(_pipe@7)
end
end
end.