use std::cmp::Ordering;
use std::fmt;
use godot_ffi as sys;
use sys::{ExtVariantType, GodotFfi, ffi_methods};
use crate::builtin::math::{FloatExt, GlamConv, GlamType};
use crate::builtin::vectors::Vector3Axis;
use crate::builtin::{Basis, RVec3, Vector2, Vector3i, inner, real};
#[doc = shared_vector_docs!()]
#[derive(Default, Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct Vector3 {
pub x: real,
pub y: real,
pub z: real,
}
impl Vector3 {
impl_vector_consts!(real);
impl_float_vector_consts!();
impl_vector3x_consts!(real);
pub const MODEL_LEFT: Self = Self::new(1.0, 0.0, 0.0);
pub const MODEL_RIGHT: Self = Self::new(-1.0, 0.0, 0.0);
pub const MODEL_TOP: Self = Self::new(0.0, 1.0, 0.0);
pub const MODEL_BOTTOM: Self = Self::new(0.0, -1.0, 0.0);
pub const MODEL_FRONT: Self = Self::new(0.0, 0.0, 1.0);
pub const MODEL_REAR: Self = Self::new(0.0, 0.0, -1.0);
}
impl_vector_fns!(Vector3, RVec3, real, (x, y, z));
impl Vector3 {
#[doc(hidden)]
#[inline]
pub fn as_inner(&self) -> inner::InnerVector3<'_> {
inner::InnerVector3::from_outer(self)
}
#[inline]
pub fn cross(self, with: Self) -> Self {
Self::from_glam(self.to_glam().cross(with.to_glam()))
}
#[inline]
pub fn octahedron_decode(uv: Vector2) -> Self {
let f = Vector2::new(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0);
let mut n = Vector3::new(f.x, f.y, 1.0 - f.x.abs() - f.y.abs());
let t = (-n.z).clamp(0.0, 1.0);
n.x += if n.x >= 0.0 { -t } else { t };
n.y += if n.y >= 0.0 { -t } else { t };
n.normalized()
}
#[inline]
pub fn octahedron_encode(self) -> Vector2 {
assert!(self.is_normalized(), "vector is not normalized!");
let mut n = self;
n /= n.x.abs() + n.y.abs() + n.z.abs();
let mut o = if n.z >= 0.0 {
Vector2::new(n.x, n.y)
} else {
let x = (1.0 - n.y.abs()) * (if n.x >= 0.0 { 1.0 } else { -1.0 });
let y = (1.0 - n.x.abs()) * (if n.y >= 0.0 { 1.0 } else { -1.0 });
Vector2::new(x, y)
};
o.x = o.x * 0.5 + 0.5;
o.y = o.y * 0.5 + 0.5;
o
}
#[inline]
pub fn outer(self, with: Self) -> Basis {
let x = Vector3::new(self.x * with.x, self.x * with.y, self.x * with.z);
let y = Vector3::new(self.y * with.x, self.y * with.y, self.y * with.z);
let z = Vector3::new(self.z * with.x, self.z * with.y, self.z * with.z);
Basis::from_rows(x, y, z)
}
#[inline]
pub fn rotated(self, axis: Self, angle: real) -> Self {
assert!(axis.is_normalized(), "axis is not normalized!");
Basis::from_axis_angle(axis, angle) * self
}
#[inline]
pub fn angle_to(self, to: Self) -> real {
self.glam2(&to, |a, b| a.angle_between(b))
}
#[inline]
pub fn signed_angle_to(self, to: Self, axis: Self) -> real {
let cross_to = self.cross(to);
let unsigned_angle = cross_to.length().atan2(self.dot(to));
let sign = cross_to.dot(axis);
if sign < 0.0 {
-unsigned_angle
} else {
unsigned_angle
}
}
#[inline]
pub fn slerp(self, to: Self, weight: real) -> Self {
let start_length_sq: real = self.length_squared();
let end_length_sq = to.length_squared();
if start_length_sq == 0.0 || end_length_sq == 0.0 {
return self.lerp(to, weight);
}
let axis = self.cross(to);
if axis == Vector3::ZERO {
return self.lerp(to, weight);
}
let unit_axis = axis.normalized();
let start_length = start_length_sq.sqrt();
let result_length = start_length.lerp(end_length_sq.sqrt(), weight);
let angle = self.angle_to(to);
self.rotated(unit_axis, angle * weight) * (result_length / start_length)
}
}
impl_float_vector_fns!(Vector3, Vector3i, (x, y, z));
impl_vector3x_fns!(Vector3, Vector2, real);
impl_vector2_vector3_fns!(Vector3, (x, y, z));
impl_vector3_vector4_fns!(Vector3, (x, y, z));
impl_vector_operators!(Vector3, real, (x, y, z));
impl fmt::Display for Vector3 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {}, {})", self.x, self.y, self.z)
}
}
unsafe impl GodotFfi for Vector3 {
const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::VECTOR3);
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
crate::meta::impl_godot_as_self!(Vector3: ByValue);
impl GlamType for RVec3 {
type Mapped = Vector3;
fn to_front(&self) -> Self::Mapped {
Vector3::new(self.x, self.y, self.z)
}
fn from_front(mapped: &Self::Mapped) -> Self {
RVec3::new(mapped.x, mapped.y, mapped.z)
}
}
#[cfg(not(feature = "double-precision"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "double-precision"))))]
impl GlamType for glam::Vec3A {
type Mapped = Vector3;
fn to_front(&self) -> Self::Mapped {
Vector3::new(self.x, self.y, self.z)
}
fn from_front(mapped: &Self::Mapped) -> Self {
glam::Vec3A::new(mapped.x, mapped.y, mapped.z)
}
}
impl GlamConv for Vector3 {
type Glam = RVec3;
}
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod test {
use super::*;
use crate::builtin::math::assert_eq_approx;
use crate::builtin::real_consts::{SQRT_2, TAU};
#[test]
#[allow(clippy::excessive_precision)]
fn rotation() {
let vector = Vector3::new(1.2, 3.4, 5.6);
assert_eq_approx!(
vector.rotated(Vector3::new(0.0, 1.0, 0.0), TAU), vector
);
assert_eq_approx!(
vector.rotated(Vector3::new(0.0, 1.0, 0.0), TAU / 4.0),
Vector3::new(5.6, 3.4, -1.2),
);
assert_eq_approx!(
vector.rotated(Vector3::new(1.0, 0.0, 0.0), TAU / 3.0),
Vector3::new(1.2, -6.54974226119285642, 0.1444863728670914),
);
assert_eq_approx!(
vector.rotated(Vector3::new(0.0, 0.0, 1.0), TAU / 2.0),
vector.rotated(Vector3::new(0.0, 0.0, 1.0), TAU / -2.0),
);
}
#[test]
fn sign() {
let vector = Vector3::new(0.2, -0.5, 0.0);
assert_eq!(vector.sign(), Vector3::new(1., -1., 0.));
}
#[test]
fn coord_min_max() {
let a = Vector3::new(1.2, 3.4, 5.6);
let b = Vector3::new(0.1, 5.6, 2.3);
assert_eq_approx!(a.coord_min(b), Vector3::new(0.1, 3.4, 2.3));
assert_eq_approx!(a.coord_max(b), Vector3::new(1.2, 5.6, 5.6));
}
#[test]
fn test_slerp() {
let vector_from = Vector3::new(0.0, 2.0, 0.0);
let vector_to = Vector3::new(2.0, 0.0, 0.0);
let vector_in_between = Vector3::new(SQRT_2, SQRT_2, 0.0);
assert_eq_approx!(vector_from.slerp(vector_to, 0.5), vector_in_between);
let vector_from = Vector3::new(0.0, 2.0, 0.0);
let vector_to = Vector3::new(0.0, -2.0, 0.0);
assert_eq_approx!(vector_from.slerp(vector_to, 0.5), Vector3::ZERO);
let vector_from = Vector3::new(0.0, 3.0, 0.0);
let vector_to = Vector3::new(0.0, 2.0, 0.0);
assert_eq_approx!(
vector_from.slerp(vector_to, 0.5),
Vector3::new(0.0, 2.5, 0.0)
);
let vector1 = Vector3::new(1.0, 2.0, 3.0);
let vector2 = Vector3::new(4.0, 5.0, 6.0);
assert_eq_approx!(
vector1.normalized().slerp(vector2.normalized(), 0.5),
Vector3::new(0.363_866_8, 0.555_698_2, 0.747_529_57)
);
assert_eq_approx!(
vector1.normalized().slerp(vector2.normalized(), 1.0 / 3.0),
Vector3::new(0.332_119_76, 0.549_413_74, 0.766_707_84)
);
assert_eq_approx!(
Vector3::new(5.0, 0.0, 0.0).slerp(Vector3::new(0.0, 3.0, 4.0), 0.5),
Vector3::new(3.535_534, 2.121_320_5, 2.828_427_3)
);
assert_eq_approx!(
Vector3::new(1.0, 1.0, 1.0).slerp(Vector3::new(2.0, 2.0, 2.0), 0.5),
Vector3::new(1.5, 1.5, 1.5)
);
assert_eq!(Vector3::ZERO.slerp(Vector3::ZERO, 0.5), Vector3::ZERO);
assert_eq!(
Vector3::ZERO.slerp(Vector3::new(1.0, 1.0, 1.0), 0.5),
Vector3::new(0.5, 0.5, 0.5)
);
assert_eq!(
Vector3::new(1.0, 1.0, 1.0).slerp(Vector3::ZERO, 0.5),
Vector3::new(0.5, 0.5, 0.5)
);
assert_eq_approx!(
Vector3::new(4.0, 6.0, 2.0).slerp(Vector3::new(8.0, 10.0, 3.0), 0.5),
Vector3::new(5.901_942_3, 8.067_587, 2.558_308)
);
assert_eq_approx!(vector1.slerp(vector2, 0.5).length(), real!(6.258_311));
}
#[test]
fn iter_sum() {
let vecs = vec![
Vector3::new(1.0, 2.0, 3.0),
Vector3::new(4.0, 5.0, 6.0),
Vector3::new(7.0, 8.0, 9.0),
];
let sum_refs = vecs.iter().sum();
let sum = vecs.into_iter().sum();
assert_eq_approx!(sum, Vector3::new(12.0, 15.0, 18.0));
assert_eq_approx!(sum_refs, Vector3::new(12.0, 15.0, 18.0));
}
#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
#[test]
fn serde_roundtrip() {
let vector = Vector3::default();
let expected_json = "{\"x\":0.0,\"y\":0.0,\"z\":0.0}";
crate::builtin::test_utils::roundtrip(&vector, expected_json);
}
}