use crate::{Quat, Vector3};
use euclid::{approxeq::ApproxEq, default, Transform3D, UnknownUnit, Vector3D};
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Basis {
pub elements: [Vector3; 3],
}
impl Default for Basis {
fn default() -> Self {
Self::identity()
}
}
impl Basis {
pub const fn identity() -> Basis {
Self {
elements: [
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
],
}
}
#[doc(hidden)]
pub fn sys(&self) -> *const sys::godot_basis {
unsafe { std::mem::transmute::<*const Basis, *const sys::godot_basis>(self as *const _) }
}
#[doc(hidden)]
pub fn from_sys(c: sys::godot_basis) -> Self {
unsafe { std::mem::transmute::<sys::godot_basis, Self>(c) }
}
pub const fn flip_x() -> Basis {
Basis::from_diagonal(Vector3::new(-1.0, 1.0, 1.0))
}
pub const fn flip_y() -> Basis {
Basis::from_diagonal(Vector3::new(1.0, -1.0, 1.0))
}
pub const fn flip_z() -> Basis {
Basis::from_diagonal(Vector3::new(1.0, 1.0, -1.0))
}
pub const fn from_diagonal(p_diag: Vector3) -> Basis {
Basis {
elements: [
Vector3::new(p_diag.x, 0.0, 0.0),
Vector3::new(0.0, p_diag.y, 0.0),
Vector3::new(0.0, 0.0, p_diag.z),
],
}
}
pub const fn from_elements(elements: [Vector3; 3]) -> Self {
Self { elements }
}
pub fn from_euler(euler: Vector3) -> Self {
let mut b = Basis::default();
b.set_euler_yxz(&euler);
b
}
pub fn from_axis_angle(axis: &Vector3, phi: f32) -> Self {
assert!(
axis.length().approx_eq(&1.0),
"The axis Vector3 must be normalized."
);
let mut basis = Basis::default();
let [x, y, z] = &mut basis.elements;
let axis_sq = Vector3::new(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z);
let cosine = phi.cos();
x.x = axis_sq.x + cosine * (1.0 - axis_sq.x);
y.y = axis_sq.y + cosine * (1.0 - axis_sq.y);
z.z = axis_sq.z + cosine * (1.0 - axis_sq.z);
let sine = phi.sin();
let t = 1.0 - cosine;
let mut xyzt = axis.x * axis.y * t;
let mut zyxs = axis.z * sine;
x.y = xyzt - zyxs;
y.x = xyzt + zyxs;
xyzt = axis.x * axis.z * t;
zyxs = axis.y * sine;
x.z = xyzt + zyxs;
z.x = xyzt - zyxs;
xyzt = axis.y * axis.z * t;
zyxs = axis.x * sine;
y.z = xyzt - zyxs;
z.y = xyzt + zyxs;
basis
}
pub fn invert(&mut self) {
let [x, y, z] = self.elements;
let co = [
y.y * z.z - y.z * z.y,
y.z * z.x - y.x * z.z,
y.x * z.y - y.y * z.x,
];
let det: f32 = x.x * co[0] + x.y * co[1] + x.z * co[2];
assert!(!det.approx_eq(&0.0), "Determinant was zero");
let s: f32 = 1.0 / det;
self.set_x(Vector3::new(co[0] * s, co[1] * s, co[2] * s));
self.set_y(Vector3::new(
(x.z * z.y - x.y * z.z) * s,
(x.x * z.z - x.z * z.x) * s,
(x.y * z.x - x.x * z.y) * s,
));
self.set_z(Vector3::new(
(x.y * y.z - x.z * y.y) * s,
(x.z * y.x - x.x * y.z) * s,
(x.x * y.y - x.y * y.x) * s,
));
}
pub fn inverted(mut self) -> Basis {
self.invert();
self
}
pub fn transpose(&mut self) {
std::mem::swap(&mut self.elements[0].y, &mut self.elements[1].x);
std::mem::swap(&mut self.elements[0].z, &mut self.elements[2].x);
std::mem::swap(&mut self.elements[1].z, &mut self.elements[2].y);
}
pub fn transposed(mut self) -> Basis {
self.transpose();
self
}
pub fn determinant(&self) -> f32 {
let [x, y, z] = &self.elements;
x.x * (y.y * z.z - z.y * y.z)
- y.x * (x.y * z.z - z.y * x.z)
+ z.x * (x.y * y.z - y.y * x.z)
}
pub fn orthonormalize(&mut self) {
assert!(
!self.determinant().approx_eq(&0.0),
"Determinant should not be zero."
);
let mut x = self.x();
let mut y = self.y();
let mut z = self.z();
x = x.normalize();
y = y - x * (x.dot(y));
y = y.normalize();
z = z - x * (x.dot(z)) - y * (y.dot(z));
z = z.normalize();
self.set_x(x);
self.set_y(y);
self.set_z(z);
}
pub fn orthonormalized(mut self) -> Basis {
self.orthonormalize();
self
}
pub fn approx_eq(&self, other: &Basis) -> bool {
return self.elements[0].approx_eq(&other.elements[0])
&& self.elements[1].approx_eq(&other.elements[1])
&& self.elements[2].approx_eq(&other.elements[2]);
}
fn is_orthogonal(&self) -> bool {
let identity = Self::identity();
let m = (*self) * self.transposed();
m.approx_eq(&identity)
}
fn is_rotation(&self) -> bool {
let det = self.determinant();
det.approx_eq(&1.0) && self.is_orthogonal()
}
pub fn rotated(self, axis: Vector3, phi: f32) -> Basis {
let rot = Basis::from_axis_angle(&axis, phi);
rot * self
}
pub fn rotate(&mut self, axis: Vector3, phi: f32) {
*self = self.rotated(axis, phi);
}
pub fn to_quat(&self) -> Quat {
let mut m = self.orthonormalized();
let det = m.determinant();
if det < 0.0 {
m.scale(&Vector3::new(-1.0, -1.0, -1.0));
}
assert!(m.is_rotation(), "Basis must be normalized in order to be casted to a Quaternion. Use to_quat() or call orthonormalized() instead.");
let trace = m.elements[0].x + m.elements[1].y + m.elements[2].z;
let mut temp = [0_f32; 4];
if trace > 0.0 {
let mut s = (trace + 1.0).sqrt();
temp[3] = s * 0.5;
s = 0.5 / s;
temp[0] = (m.elements[2].y - m.elements[1].z) * s;
temp[1] = (m.elements[0].z - m.elements[2].x) * s;
temp[2] = (m.elements[1].x - m.elements[0].y) * s;
} else {
let i = if m.elements[0].x < m.elements[1].y {
if m.elements[1].y < m.elements[2].z {
2
} else {
1
}
} else {
if m.elements[0].x < m.elements[2].z {
2
} else {
0
}
};
let j = (i + 1) % 3;
let k = (i + 2) % 3;
let elements_arr: [[f32; 3]; 3] = [
m.elements[0].to_array(),
m.elements[1].to_array(),
m.elements[2].to_array(),
];
let mut s = (elements_arr[i][i] - elements_arr[j][j] - elements_arr[k][k] + 1.0).sqrt();
temp[i] = s * 0.5;
s = 0.5 / s;
temp[3] = (elements_arr[k][j] - elements_arr[j][k]) * s;
temp[j] = (elements_arr[j][i] + elements_arr[i][j]) * s;
temp[k] = (elements_arr[k][i] + elements_arr[i][k]) * s;
}
let [a, b, c, r] = temp;
Quat::quaternion(a, b, c, r)
}
pub fn to_scale(&self) -> Vector3 {
let det = self.determinant();
let det_sign = if det < 0.0 { -1.0 } else { 1.0 };
Vector3::new(
Vector3::new(self.elements[0].x, self.elements[1].x, self.elements[2].x).length(),
Vector3::new(self.elements[0].y, self.elements[1].y, self.elements[2].y).length(),
Vector3::new(self.elements[0].z, self.elements[1].z, self.elements[2].z).length(),
) * det_sign
}
fn scale(&mut self, s: &Vector3) {
self.elements[0] *= s.x;
self.elements[1] *= s.y;
self.elements[2] *= s.z;
}
pub fn scaled(mut self, scale: &Vector3) -> Basis {
self.scale(scale);
self
}
pub fn to_euler(&self) -> Vector3 {
let mut euler = Vector3::zero();
let m12 = self.elements[1].z;
if m12 < 1.0 {
if m12 > -1.0 {
if self.elements[1].x.approx_eq(&0.0)
&& self.elements[0].y.approx_eq(&0.0)
&& self.elements[0].z.approx_eq(&0.0)
&& self.elements[2].x.approx_eq(&0.0)
&& self.elements[0].x.approx_eq(&1.0)
{
euler.x = (-m12).atan2(self.elements[1].y);
euler.y = 0.0;
euler.z = 0.0;
} else {
euler.x = (-m12).asin();
euler.y = self.elements[0].z.atan2(self.elements[2].z);
euler.z = self.elements[1].x.atan2(self.elements[1].y);
}
} else {
euler.x = core::f32::consts::PI * 0.5;
euler.y = -(-self.elements[0].y).atan2(self.elements[0].x);
euler.z = 0.0;
}
} else {
euler.x = -core::f32::consts::PI * 0.5;
euler.y = -(-self.elements[0].y).atan2(self.elements[0].x);
euler.z = 0.0;
}
euler
}
fn set_euler_yxz(&mut self, euler: &Vector3) {
let c = euler.x.cos();
let s = euler.x.sin();
let xmat = Basis::from_elements([
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, c, -s),
Vector3::new(0.0, s, c),
]);
let c = euler.y.cos();
let s = euler.y.sin();
let ymat = Basis::from_elements([
Vector3::new(c, 0.0, s),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(-s, 0.0, c),
]);
let c = euler.z.cos();
let s = euler.z.sin();
let zmat = Basis::from_elements([
Vector3::new(c, -s, 0.0),
Vector3::new(s, c, 0.0),
Vector3::new(0.0, 0.0, 1.0),
]);
*self = ymat * xmat * zmat;
}
pub fn xform(&self, v: Vector3) -> Vector3 {
Vector3::new(
self.elements[0].dot(v),
self.elements[1].dot(v),
self.elements[2].dot(v),
)
}
pub fn xform_inv(&self, v: Vector3) -> Vector3 {
Vector3::new(
(self.elements[0].x * v.x) + (self.elements[1].x * v.y) + (self.elements[2].x * v.z),
(self.elements[0].y * v.x) + (self.elements[1].y * v.y) + (self.elements[2].y * v.z),
(self.elements[0].z * v.x) + (self.elements[1].z * v.y) + (self.elements[2].z * v.z),
)
}
pub fn from_transform(transform: &default::Transform3D<f32>) -> Basis {
Self::from_typed_transform::<UnknownUnit, UnknownUnit>(transform)
}
pub fn from_typed_transform<Src, Dst>(transform: &Transform3D<f32, Src, Dst>) -> Basis {
Basis {
elements: [
transform
.transform_vector3d(Vector3D::<_, Src>::new(1.0, 0.0, 0.0))
.to_untyped(),
transform
.transform_vector3d(Vector3D::<_, Src>::new(0.0, 1.0, 0.0))
.to_untyped(),
transform
.transform_vector3d(Vector3D::<_, Src>::new(0.0, 0.0, 1.0))
.to_untyped(),
],
}
}
pub fn tdotx(&self, v: Vector3) -> f32 {
self.elements[0].x * v.x + self.elements[1].x * v.y + self.elements[2].x * v.z
}
pub fn tdoty(&self, v: Vector3) -> f32 {
self.elements[0].y * v.x + self.elements[1].y * v.y + self.elements[2].y * v.z
}
pub fn tdotz(&self, v: Vector3) -> f32 {
self.elements[0].z * v.x + self.elements[1].z * v.y + self.elements[2].z * v.z
}
pub fn x(&self) -> Vector3 {
Vector3::new(self.elements[0].x, self.elements[1].x, self.elements[2].x)
}
pub fn set_x(&mut self, v: Vector3) {
self.elements[0].x = v.x;
self.elements[1].x = v.y;
self.elements[2].x = v.z;
}
pub fn y(&self) -> Vector3 {
Vector3::new(self.elements[0].y, self.elements[1].y, self.elements[2].y)
}
pub fn set_y(&mut self, v: Vector3) {
self.elements[0].y = v.x;
self.elements[1].y = v.y;
self.elements[2].y = v.z;
}
pub fn z(&self) -> Vector3 {
Vector3::new(self.elements[0].z, self.elements[1].z, self.elements[2].z)
}
pub fn set_z(&mut self, v: Vector3) {
self.elements[0].z = v.x;
self.elements[1].z = v.y;
self.elements[2].z = v.z;
}
}
impl core::ops::Mul<Basis> for Basis {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
return Basis::from_elements([
Vector3::new(
rhs.tdotx(self.elements[0]),
rhs.tdoty(self.elements[0]),
rhs.tdotz(self.elements[0]),
),
Vector3::new(
rhs.tdotx(self.elements[1]),
rhs.tdoty(self.elements[1]),
rhs.tdotz(self.elements[1]),
),
Vector3::new(
rhs.tdotx(self.elements[2]),
rhs.tdoty(self.elements[2]),
rhs.tdotz(self.elements[2]),
),
]);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transposed_dot_is_sane() {
let basis = Basis {
elements: [
Vector3::new(1.0, 2.0, 3.0),
Vector3::new(2.0, 3.0, 4.0),
Vector3::new(3.0, 4.0, 5.0),
],
};
let vector = Vector3::new(4.0, 5.0, 6.0);
assert!((basis.tdotx(vector) - 32.0).abs() < std::f32::EPSILON);
assert!((basis.tdoty(vector) - 47.0).abs() < std::f32::EPSILON);
assert!((basis.tdotz(vector) - 62.0).abs() < std::f32::EPSILON);
}
#[test]
fn retrieval_is_sane() {
let basis = Basis {
elements: [
Vector3::new(1.0, 2.0, 3.0),
Vector3::new(4.0, 5.0, 6.0),
Vector3::new(7.0, 8.0, 9.0),
],
};
assert!(basis.x() == Vector3::new(1.0, 4.0, 7.0));
assert!(basis.y() == Vector3::new(2.0, 5.0, 8.0));
assert!(basis.z() == Vector3::new(3.0, 6.0, 9.0));
}
#[test]
fn set_is_sane() {
let mut basis = Basis {
elements: [Vector3::zero(), Vector3::zero(), Vector3::zero()],
};
basis.set_x(Vector3::new(1.0, 4.0, 7.0));
basis.set_y(Vector3::new(2.0, 5.0, 8.0));
basis.set_z(Vector3::new(3.0, 6.0, 9.0));
assert!(basis.elements[0] == Vector3::new(1.0, 2.0, 3.0));
assert!(basis.elements[1] == Vector3::new(4.0, 5.0, 6.0));
assert!(basis.elements[2] == Vector3::new(7.0, 8.0, 9.0));
}
fn test_inputs() -> (Basis, Basis) {
let v = Vector3::new(37.51756, 20.39467, 49.96816);
let vn = v.normalize();
let b = Basis::from_euler(v);
let bn = Basis::from_euler(vn);
(b, bn)
}
#[test]
fn determinant() {
let (b, _bn) = test_inputs();
assert!(b.determinant().approx_eq(&1.0), "Determinant should be 1.0");
}
#[test]
fn euler() {
let (_b, bn) = test_inputs();
assert!(Vector3::new(0.57079, 0.310283, 0.760213).approx_eq(&bn.to_euler()));
}
#[test]
fn orthonormalized() {
let (b, _bn) = test_inputs();
let expected = Basis::from_elements([
Vector3::new(0.077431, -0.165055, 0.98324),
Vector3::new(-0.288147, 0.94041, 0.180557),
Vector3::new(-0.95445, -0.297299, 0.025257),
]);
assert!(expected.approx_eq(&b.orthonormalized()));
}
#[test]
fn scaled() {
let (b, _bn) = test_inputs();
let expected = Basis::from_elements([
Vector3::new(0.052484, -0.111876, 0.666453),
Vector3::new(0.012407, -0.040492, -0.007774),
Vector3::new(-0.682131, -0.212475, 0.018051),
]);
assert!(expected.approx_eq(&b.scaled(&Vector3::new(0.677813, -0.043058, 0.714685))));
}
#[test]
fn rotated() {
let (b, _bn) = test_inputs();
let r = Vector3::new(-50.167156, 60.677813, -70.043058).normalize();
let expected = Basis::from_elements([
Vector3::new(-0.676245, 0.113805, 0.727833),
Vector3::new(-0.467094, 0.697765, -0.54309),
Vector3::new(-0.569663, -0.707229, -0.418703),
]);
assert!(expected.approx_eq(&b.rotated(r, 1.0)));
}
#[test]
fn to_quat() {
let (b, _bn) = test_inputs();
assert!(Quat::quaternion(-0.167156, 0.677813, -0.043058, 0.714685).approx_eq(&b.to_quat()));
}
#[test]
fn scale() {
let (b, _bn) = test_inputs();
assert!(Vector3::new(1.0, 1.0, 1.0).approx_eq(&b.to_scale()));
}
#[test]
fn approx_eq() {
let (b, _bn) = test_inputs();
assert!(!b.approx_eq(&Basis::from_euler(Vector3::new(37.517, 20.394, 49.968))));
}
#[test]
fn transposed() {
let (b, _bn) = test_inputs();
let expected = Basis::from_elements([
Vector3::new(0.077431, -0.288147, -0.95445),
Vector3::new(-0.165055, 0.94041, -0.297299),
Vector3::new(0.98324, 0.180557, 0.025257),
]);
assert!(expected.approx_eq(&b.transposed()));
}
#[test]
fn xform() {
let (b, _bn) = test_inputs();
assert!(Vector3::new(-0.273471, 0.478102, -0.690386)
.approx_eq(&b.xform(Vector3::new(0.5, 0.7, -0.2))));
}
#[test]
fn xform_inv() {
let (b, _bn) = test_inputs();
assert!(Vector3::new(-0.884898, -0.460316, 0.071165)
.approx_eq(&b.xform_inv(Vector3::new(0.077431, -0.165055, 0.98324))));
}
#[test]
fn inverse() {
let (b, _bn) = test_inputs();
let expected = Basis::from_elements([
Vector3::new(0.077431, -0.288147, -0.95445),
Vector3::new(-0.165055, 0.94041, -0.297299),
Vector3::new(0.98324, 0.180557, 0.025257),
]);
assert!(expected.approx_eq(&b.inverted()));
}
}