Skip to content

Commit 6dcc86f

Browse files
author
bors-servo
authored
Auto merge of #373 - nical:extra-methods, r=kvark
Add a few methods to Vector2D and Vector3D Here are some methods that have been requested to me through another project that uses euclid (godot bindings) and are universal and simple enough to have their place in euclid in my opinion: - `angle_to` between two vectors is cheaper and nicer than calling `angle_from_x_axis` twice. It's also implemented in 3D. - `cap_length` to ensure the length of a vector isn't bigger than some provided quantity, typically used a lot of in gameplay code to put limits on things. I originally named it `clamp_length` but since we alread have `clamp` to enforce both a min and a max value I suspected the inconsistency might be controversial. - `bounce` is also arguably more useful to games than browsers. You'd use it to prototype a slow ray tracer or code some physical behavior. - `project_onto` projects a vector onto another vector. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/euclid/373) <!-- Reviewable:end -->
2 parents a79bec6 + 5298893 commit 6dcc86f

File tree

2 files changed

+298
-2
lines changed

2 files changed

+298
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "euclid"
3-
version = "0.20.2"
3+
version = "0.20.3"
44
authors = ["The Servo Project Developers"]
55
description = "Geometry primitives"
66
documentation = "https://docs.rs/euclid/"

src/vector.rs

Lines changed: 297 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,27 @@ impl<T, U> Vector2D<T, U>
226226
where
227227
T: Trig + Copy + Sub<T, Output = T>,
228228
{
229-
/// Returns the angle between this vector and the x axis between -PI and PI.
229+
/// Returns the signed angle between this vector and the x axis.
230+
///
231+
/// The returned angle is between -PI and PI.
230232
pub fn angle_from_x_axis(&self) -> Angle<T> {
231233
Angle::radians(Trig::fast_atan2(self.y, self.x))
232234
}
233235
}
234236

237+
impl<T, U> Vector2D<T, U>
238+
where
239+
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>
240+
+ Trig + Copy + Sub<T, Output = T>,
241+
{
242+
/// Returns the signed angle between this vector and another vector.
243+
///
244+
/// The returned angle is between -PI and PI.
245+
pub fn angle_to(&self, other: Self) -> Angle<T> {
246+
Angle::radians(Trig::fast_atan2(self.cross(other), self.dot(other)))
247+
}
248+
}
249+
235250
impl<T, U> Vector2D<T, U>
236251
where
237252
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>,
@@ -283,6 +298,52 @@ where
283298
{
284299
self.square_length().sqrt()
285300
}
301+
302+
/// Returns this vector projected onto another one.
303+
///
304+
/// Projecting onto a nil vector will cause a division by zero.
305+
#[inline]
306+
pub fn project_onto_vector(&self, onto: Self) -> Self
307+
where
308+
T: Div<T, Output = T>
309+
{
310+
onto * (self.dot(onto) / onto.square_length())
311+
}
312+
}
313+
314+
impl<T, U> Vector2D<T, U>
315+
where
316+
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>
317+
+ PartialOrd + Float
318+
{
319+
/// Return this vector capped to a maximum length.
320+
#[inline]
321+
pub fn with_max_length(&self, max_length: T) -> Self {
322+
let square_length = self.square_length();
323+
if square_length > max_length * max_length {
324+
return (*self) * (max_length / square_length.sqrt());
325+
}
326+
327+
*self
328+
}
329+
330+
/// Return this vector with a minimum length applied.
331+
#[inline]
332+
pub fn with_min_length(&self, min_length: T) -> Self {
333+
let square_length = self.square_length();
334+
if square_length < min_length * min_length {
335+
return (*self) * (min_length / square_length.sqrt());
336+
}
337+
338+
*self
339+
}
340+
341+
/// Return this vector with minimum and maximum lengths applied.
342+
#[inline]
343+
pub fn clamp_length(&self, min: T, max: T) -> Self {
344+
debug_assert!(min <= max);
345+
self.with_min_length(min).with_max_length(max)
346+
}
286347
}
287348

288349
impl<T, U> Vector2D<T, U>
@@ -299,6 +360,18 @@ where
299360
}
300361
}
301362

363+
impl<T, U> Vector2D<T, U>
364+
where
365+
T: Copy + One + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>,
366+
{
367+
/// Returns a reflection vector using an incident ray and a surface normal.
368+
#[inline]
369+
pub fn reflect(&self, normal: Self) -> Self {
370+
let two = T::one() + T::one();
371+
*self - normal * two * self.dot(normal)
372+
}
373+
}
374+
302375
impl<T: Copy + Add<T, Output = T>, U> Add for Vector2D<T, U> {
303376
type Output = Self;
304377
fn add(self, other: Self) -> Self {
@@ -768,6 +841,20 @@ where
768841
}
769842
}
770843

844+
impl<T, U> Vector3D<T, U>
845+
where
846+
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>
847+
+ Trig + Copy + Sub<T, Output = T>
848+
+ Float
849+
{
850+
/// Returns the positive angle between this vector and another vector.
851+
///
852+
/// The returned angle is between 0 and PI.
853+
pub fn angle_to(&self, other: Self) -> Angle<T> {
854+
Angle::radians(Trig::fast_atan2(self.cross(other).length(), self.dot(other)))
855+
}
856+
}
857+
771858
impl<T: Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Copy, U>
772859
Vector3D<T, U> {
773860
// Dot product.
@@ -821,6 +908,52 @@ impl<T: Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Copy, U>
821908
{
822909
self.square_length().sqrt()
823910
}
911+
912+
/// Returns this vector projected onto another one.
913+
///
914+
/// Projecting onto a nil vector will cause a division by zero.
915+
#[inline]
916+
pub fn project_onto_vector(&self, onto: Self) -> Self
917+
where
918+
T: Div<T, Output = T>
919+
{
920+
onto * (self.dot(onto) / onto.square_length())
921+
}
922+
}
923+
924+
impl<T, U> Vector3D<T, U>
925+
where
926+
T: Copy + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>
927+
+ PartialOrd + Float
928+
{
929+
/// Return this vector capped to a maximum length.
930+
#[inline]
931+
pub fn with_max_length(&self, max_length: T) -> Self {
932+
let square_length = self.square_length();
933+
if square_length > max_length * max_length {
934+
return (*self) * (max_length / square_length.sqrt());
935+
}
936+
937+
*self
938+
}
939+
940+
/// Return this vector with a minimum length applied.
941+
#[inline]
942+
pub fn with_min_length(&self, min_length: T) -> Self {
943+
let square_length = self.square_length();
944+
if square_length < min_length * min_length {
945+
return (*self) * (min_length / square_length.sqrt());
946+
}
947+
948+
*self
949+
}
950+
951+
/// Return this vector with minimum and maximum lengths applied.
952+
#[inline]
953+
pub fn clamp_length(&self, min: T, max: T) -> Self {
954+
debug_assert!(min <= max);
955+
self.with_min_length(min).with_max_length(max)
956+
}
824957
}
825958

826959
impl<T, U> Vector3D<T, U>
@@ -837,6 +970,18 @@ where
837970
}
838971
}
839972

973+
impl<T, U> Vector3D<T, U>
974+
where
975+
T: Copy + One + Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T>,
976+
{
977+
/// Returns a reflection vector using an incident ray and a surface normal.
978+
#[inline]
979+
pub fn reflect(&self, normal: Self) -> Self {
980+
let two = T::one() + T::one();
981+
*self - normal * two * self.dot(normal)
982+
}
983+
}
984+
840985
impl<T: Copy + Add<T, Output = T>, U> Add for Vector3D<T, U> {
841986
type Output = Self;
842987
#[inline]
@@ -1458,6 +1603,7 @@ mod vector2d {
14581603

14591604
assert_eq!(result, vec2(2.0, 3.0));
14601605
}
1606+
14611607
#[test]
14621608
pub fn test_angle_from_x_axis() {
14631609
use core::f32::consts::FRAC_PI_2;
@@ -1472,6 +1618,69 @@ mod vector2d {
14721618
assert!(up.angle_from_x_axis().get().approx_eq(&-FRAC_PI_2));
14731619
}
14741620

1621+
#[test]
1622+
pub fn test_angle_to() {
1623+
use core::f32::consts::FRAC_PI_2;
1624+
use approxeq::ApproxEq;
1625+
1626+
let right: Vec2 = vec2(10.0, 0.0);
1627+
let right2: Vec2 = vec2(1.0, 0.0);
1628+
let up: Vec2 = vec2(0.0, -1.0);
1629+
let up_left: Vec2 = vec2(-1.0, -1.0);
1630+
1631+
assert!(right.angle_to(right2).get().approx_eq(&0.0));
1632+
assert!(right.angle_to(up).get().approx_eq(&-FRAC_PI_2));
1633+
assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2));
1634+
assert!(up_left.angle_to(up).get().approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005));
1635+
}
1636+
1637+
#[test]
1638+
pub fn test_with_max_length() {
1639+
use approxeq::ApproxEq;
1640+
1641+
let v1: Vec2 = vec2(0.5, 0.5);
1642+
let v2: Vec2 = vec2(1.0, 0.0);
1643+
let v3: Vec2 = vec2(0.1, 0.2);
1644+
let v4: Vec2 = vec2(2.0, -2.0);
1645+
let v5: Vec2 = vec2(1.0, 2.0);
1646+
let v6: Vec2 = vec2(-1.0, 3.0);
1647+
1648+
assert_eq!(v1.with_max_length(1.0), v1);
1649+
assert_eq!(v2.with_max_length(1.0), v2);
1650+
assert_eq!(v3.with_max_length(1.0), v3);
1651+
assert_eq!(v4.with_max_length(10.0), v4);
1652+
assert_eq!(v5.with_max_length(10.0), v5);
1653+
assert_eq!(v6.with_max_length(10.0), v6);
1654+
1655+
let v4_clamped = v4.with_max_length(1.0);
1656+
assert!(v4_clamped.length().approx_eq(&1.0));
1657+
assert!(v4_clamped.normalize().approx_eq(&v4.normalize()));
1658+
1659+
let v5_clamped = v5.with_max_length(1.5);
1660+
assert!(v5_clamped.length().approx_eq(&1.5));
1661+
assert!(v5_clamped.normalize().approx_eq(&v5.normalize()));
1662+
1663+
let v6_clamped = v6.with_max_length(2.5);
1664+
assert!(v6_clamped.length().approx_eq(&2.5));
1665+
assert!(v6_clamped.normalize().approx_eq(&v6.normalize()));
1666+
}
1667+
1668+
#[test]
1669+
pub fn test_project_onto_vector() {
1670+
use approxeq::ApproxEq;
1671+
1672+
let v1: Vec2 = vec2(1.0, 2.0);
1673+
let x: Vec2 = vec2(1.0, 0.0);
1674+
let y: Vec2 = vec2(0.0, 1.0);
1675+
1676+
assert!(v1.project_onto_vector(x).approx_eq(&vec2(1.0, 0.0)));
1677+
assert!(v1.project_onto_vector(y).approx_eq(&vec2(0.0, 2.0)));
1678+
assert!(v1.project_onto_vector(-x).approx_eq(&vec2(1.0, 0.0)));
1679+
assert!(v1.project_onto_vector(x * 10.0).approx_eq(&vec2(1.0, 0.0)));
1680+
assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1));
1681+
assert!(v1.project_onto_vector(-v1).approx_eq(&v1));
1682+
}
1683+
14751684
#[cfg(feature = "mint")]
14761685
#[test]
14771686
pub fn test_mint() {
@@ -1521,6 +1730,17 @@ mod vector2d {
15211730
let p: default::Vector2D<i32> = vec2(1, 2);
15221731
assert_eq!(p.yx(), vec2(2, 1));
15231732
}
1733+
1734+
#[test]
1735+
pub fn test_reflect() {
1736+
use approxeq::ApproxEq;
1737+
let a: Vec2 = vec2(1.0, 3.0);
1738+
let n1: Vec2 = vec2(0.0, -1.0);
1739+
let n2: Vec2 = vec2(1.0, -1.0).normalize();
1740+
1741+
assert!(a.reflect(n1).approx_eq(&vec2(1.0, -3.0)));
1742+
assert!(a.reflect(n2).approx_eq(&vec2(3.0, 1.0)));
1743+
}
15241744
}
15251745

15261746
#[cfg(test)]
@@ -1624,6 +1844,82 @@ mod vector3d {
16241844

16251845
assert_eq!(v1, v2);
16261846
}
1847+
1848+
#[test]
1849+
pub fn test_reflect() {
1850+
use approxeq::ApproxEq;
1851+
let a: Vec3 = vec3(1.0, 3.0, 2.0);
1852+
let n1: Vec3 = vec3(0.0, -1.0, 0.0);
1853+
let n2: Vec3 = vec3(0.0, 1.0, 1.0).normalize();
1854+
1855+
assert!(a.reflect(n1).approx_eq(&vec3(1.0, -3.0, 2.0)));
1856+
assert!(a.reflect(n2).approx_eq(&vec3(1.0, -2.0, -3.0)));
1857+
}
1858+
1859+
#[test]
1860+
pub fn test_angle_to() {
1861+
use core::f32::consts::FRAC_PI_2;
1862+
use approxeq::ApproxEq;
1863+
1864+
let right: Vec3 = vec3(10.0, 0.0, 0.0);
1865+
let right2: Vec3 = vec3(1.0, 0.0, 0.0);
1866+
let up: Vec3 = vec3(0.0, -1.0, 0.0);
1867+
let up_left: Vec3 = vec3(-1.0, -1.0, 0.0);
1868+
1869+
assert!(right.angle_to(right2).get().approx_eq(&0.0));
1870+
assert!(right.angle_to(up).get().approx_eq(&FRAC_PI_2));
1871+
assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2));
1872+
assert!(up_left.angle_to(up).get().approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005));
1873+
}
1874+
1875+
#[test]
1876+
pub fn test_with_max_length() {
1877+
use approxeq::ApproxEq;
1878+
1879+
let v1: Vec3 = vec3(0.5, 0.5, 0.0);
1880+
let v2: Vec3 = vec3(1.0, 0.0, 0.0);
1881+
let v3: Vec3 = vec3(0.1, 0.2, 0.3);
1882+
let v4: Vec3 = vec3(2.0, -2.0, 2.0);
1883+
let v5: Vec3 = vec3(1.0, 2.0, -3.0);
1884+
let v6: Vec3 = vec3(-1.0, 3.0, 2.0);
1885+
1886+
assert_eq!(v1.with_max_length(1.0), v1);
1887+
assert_eq!(v2.with_max_length(1.0), v2);
1888+
assert_eq!(v3.with_max_length(1.0), v3);
1889+
assert_eq!(v4.with_max_length(10.0), v4);
1890+
assert_eq!(v5.with_max_length(10.0), v5);
1891+
assert_eq!(v6.with_max_length(10.0), v6);
1892+
1893+
let v4_clamped = v4.with_max_length(1.0);
1894+
assert!(v4_clamped.length().approx_eq(&1.0));
1895+
assert!(v4_clamped.normalize().approx_eq(&v4.normalize()));
1896+
1897+
let v5_clamped = v5.with_max_length(1.5);
1898+
assert!(v5_clamped.length().approx_eq(&1.5));
1899+
assert!(v5_clamped.normalize().approx_eq(&v5.normalize()));
1900+
1901+
let v6_clamped = v6.with_max_length(2.5);
1902+
assert!(v6_clamped.length().approx_eq(&2.5));
1903+
assert!(v6_clamped.normalize().approx_eq(&v6.normalize()));
1904+
}
1905+
1906+
#[test]
1907+
pub fn test_project_onto_vector() {
1908+
use approxeq::ApproxEq;
1909+
1910+
let v1: Vec3 = vec3(1.0, 2.0, 3.0);
1911+
let x: Vec3 = vec3(1.0, 0.0, 0.0);
1912+
let y: Vec3 = vec3(0.0, 1.0, 0.0);
1913+
let z: Vec3 = vec3(0.0, 0.0, 1.0);
1914+
1915+
assert!(v1.project_onto_vector(x).approx_eq(&vec3(1.0, 0.0, 0.0)));
1916+
assert!(v1.project_onto_vector(y).approx_eq(&vec3(0.0, 2.0, 0.0)));
1917+
assert!(v1.project_onto_vector(z).approx_eq(&vec3(0.0, 0.0, 3.0)));
1918+
assert!(v1.project_onto_vector(-x).approx_eq(&vec3(1.0, 0.0, 0.0)));
1919+
assert!(v1.project_onto_vector(x * 10.0).approx_eq(&vec3(1.0, 0.0, 0.0)));
1920+
assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1));
1921+
assert!(v1.project_onto_vector(-v1).approx_eq(&v1));
1922+
}
16271923
}
16281924

16291925
#[cfg(test)]

0 commit comments

Comments
 (0)