Skip to content

Commit

Permalink
Auto merge of #243 - nical:angle, r=kvark
Browse files Browse the repository at this point in the history
Use a dedicated Angle type for angles instead of Length<Rad>.

This is the other breaking change that I want to sneak in @SimonSapin's bump.

Length has proved quite unergonomic for angles. We don't have a good use case for storing angles in degrees (that would be evil), and there could be a lot useful specific methods for methods that apply for angles but not lengths.

With a dedicated angle type we can write things like:
```rust
mat.pre_rotate(Angle::radians(PI));
mat.pre_rotate(Angle::degrees(90.0));
```
which deals with degrees in a nicer way (everything is always stored in radians but conversion to and from is easy).
Also, with Angle we can implement `Angle<T> / Angle<T> = T` to compute ratios, multiply angles by these ratios, etc.

I ran into the (lack of) ergonomics with `Length<Rad>` a lot in the SVG logic in lyon.

<!-- 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/243)
<!-- Reviewable:end -->
  • Loading branch information
bors-servo authored Dec 7, 2017
2 parents a6f466d + ff9b61c commit 58904ae
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 66 deletions.
18 changes: 5 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub use vector::{
};

pub use rect::{Rect, TypedRect, rect};
pub use rotation::{TypedRotation2D, Rotation2D, TypedRotation3D, Rotation3D};
pub use rotation::{TypedRotation2D, Rotation2D, TypedRotation3D, Rotation3D, Angle};
pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D};
#[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32;
pub use size::{Size2D, TypedSize2D, size2};
Expand All @@ -105,18 +105,6 @@ mod vector;
#[derive(Clone, Copy)]
pub struct UnknownUnit;

/// Unit for angles in radians.
pub struct Rad;

/// Unit for angles in degrees.
pub struct Deg;

/// A value in radians.
pub type Radians<T> = Length<T, Rad>;

/// A value in Degrees.
pub type Degrees<T> = Length<T, Deg>;

/// Temporary alias to facilitate the transition to the new naming scheme
#[deprecated]
pub type Matrix2D<T> = Transform2D<T>;
Expand All @@ -136,3 +124,7 @@ pub type TypedMatrix4D<T, Src, Dst> = TypedTransform3D<T, Src, Dst>;
/// Temporary alias to facilitate the transition to the new naming scheme
#[deprecated]
pub type ScaleFactor<T, Src, Dst> = TypedScale<T, Src, Dst>;

/// Temporary alias to facilitate the transition to the new naming scheme
#[deprecated]
pub use Angle as Radians;
169 changes: 132 additions & 37 deletions src/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,106 @@
use approxeq::ApproxEq;
use num_traits::{Float, One, Zero};
use std::fmt;
use std::ops::{Add, Neg, Mul, Sub, Div};
use std::ops::{Add, Neg, Mul, Sub, Div, AddAssign, SubAssign, MulAssign, DivAssign};
use std::marker::PhantomData;
use trig::Trig;
use {TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D, Vector3D, point2, point3, vec3};
use {TypedTransform3D, TypedTransform2D, UnknownUnit, Radians};
use {TypedTransform3D, TypedTransform2D, UnknownUnit};

/// An angle in radians
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)]
pub struct Angle<T> {
pub radians: T,
}

impl<T> Angle<T> {
#[inline]
pub fn radians(radians: T) -> Self {
Angle { radians }
}

#[inline]
pub fn get(self) -> T {
self.radians
}
}

impl<T> Angle<T>
where T: Trig
{
#[inline]
pub fn degrees(deg: T) -> Self {
Angle { radians: T::degrees_to_radians(deg) }
}

#[inline]
pub fn to_degrees(self) -> T {
T::radians_to_degrees(self.radians)
}
}

impl<T: Clone + Add<T, Output=T>> Add for Angle<T> {
type Output = Angle<T>;
fn add(self, other: Angle<T>) -> Angle<T> {
Angle::radians(self.radians + other.radians)
}
}

impl<T: Clone + AddAssign<T>> AddAssign for Angle<T> {
fn add_assign(&mut self, other: Angle<T>) {
self.radians += other.radians;
}
}

impl<T: Clone + Sub<T, Output=T>> Sub<Angle<T>> for Angle<T> {
type Output = Angle<T>;
fn sub(self, other: Angle<T>) -> <Self as Sub>::Output {
Angle::radians(self.radians - other.radians)
}
}

impl<T: Clone + SubAssign<T>> SubAssign for Angle<T> {
fn sub_assign(&mut self, other: Angle<T>) {
self.radians -= other.radians;
}
}

impl<T: Clone + Div<T, Output=T>> Div<Angle<T>> for Angle<T> {
type Output = T;
#[inline]
fn div(self, other: Angle<T>) -> T {
self.radians / other.radians
}
}

impl<T: Clone + Div<T, Output=T>> Div<T> for Angle<T> {
type Output = Angle<T>;
#[inline]
fn div(self, factor: T) -> Angle<T> {
Angle::radians(self.radians / factor)
}
}

impl<T: Clone + DivAssign<T>> DivAssign<T> for Angle<T> {
fn div_assign(&mut self, factor: T) {
self.radians /= factor;
}
}

impl<T: Clone + Mul<T, Output=T>> Mul<T> for Angle<T> {
type Output = Angle<T>;
#[inline]
fn mul(self, factor: T) -> Angle<T> {
Angle::radians(self.radians * factor)
}
}

impl<T: Clone + MulAssign<T>> MulAssign<T> for Angle<T> {
fn mul_assign(&mut self, factor: T) {
self.radians *= factor;
}
}


define_matrix! {
/// A transform that can represent rotations in 2d, represented as an angle in radians.
Expand All @@ -29,15 +124,15 @@ pub type Rotation2D<T> = TypedRotation2D<T, UnknownUnit, UnknownUnit>;
impl<T, Src, Dst> TypedRotation2D<T, Src, Dst> {
#[inline]
/// Creates a rotation from an angle in radians.
pub fn new(angle: Radians<T>) -> Self {
pub fn new(angle: Angle<T>) -> Self {
TypedRotation2D {
angle: angle.0,
angle: angle.radians,
_unit: PhantomData,
}
}

pub fn radians(angle: T) -> Self {
Self::new(Radians::new(angle))
Self::new(Angle::radians(angle))
}

/// Creates the identity rotation.
Expand All @@ -49,9 +144,9 @@ impl<T, Src, Dst> TypedRotation2D<T, Src, Dst> {

impl<T, Src, Dst> TypedRotation2D<T, Src, Dst> where T: Clone
{
/// Returns self.angle as a strongly typed `Radians<T>`.
pub fn get_angle(&self) -> Radians<T> {
Radians::new(self.angle.clone())
/// Returns self.angle as a strongly typed `Angle<T>`.
pub fn get_angle(&self) -> Angle<T> {
Angle::radians(self.angle.clone())
}
}

Expand Down Expand Up @@ -207,34 +302,34 @@ where T: Copy + Clone +
}

/// Creates a rotation around a given axis.
pub fn around_axis(axis: TypedVector3D<T, Src>, angle: Radians<T>) -> Self {
pub fn around_axis(axis: TypedVector3D<T, Src>, angle: Angle<T>) -> Self {
let axis = axis.normalize();
let two = T::one() + T::one();
let (sin, cos) = Float::sin_cos(angle.get() / two);
let (sin, cos) = Float::sin_cos(angle.radians / two);
Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos)
}

/// Creates a rotation around the x axis.
pub fn around_x(angle: Radians<T>) -> Self {
pub fn around_x(angle: Angle<T>) -> Self {
let zero = Zero::zero();
let two = T::one() + T::one();
let (sin, cos) = Float::sin_cos(angle.get() / two);
let (sin, cos) = Float::sin_cos(angle.radians / two);
Self::quaternion(sin, zero, zero, cos)
}

/// Creates a rotation around the y axis.
pub fn around_y(angle: Radians<T>) -> Self {
pub fn around_y(angle: Angle<T>) -> Self {
let zero = Zero::zero();
let two = T::one() + T::one();
let (sin, cos) = Float::sin_cos(angle.get() / two);
let (sin, cos) = Float::sin_cos(angle.radians / two);
Self::quaternion(zero, sin, zero, cos)
}

/// Creates a rotation around the z axis.
pub fn around_z(angle: Radians<T>) -> Self {
pub fn around_z(angle: Angle<T>) -> Self {
let zero = Zero::zero();
let two = T::one() + T::one();
let (sin, cos) = Float::sin_cos(angle.get() / two);
let (sin, cos) = Float::sin_cos(angle.radians / two);
Self::quaternion(zero, zero, sin, cos)
}

Expand All @@ -245,7 +340,7 @@ where T: Copy + Clone +
/// - Roll (also calld bank) is a rotation around the x axis.
/// - Pitch (also calld bearing) is a rotation around the y axis.
/// - Yaw (also calld heading) is a rotation around the z axis.
pub fn euler(roll: Radians<T>, pitch: Radians<T>, yaw: Radians<T>) -> Self {
pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self {
let half = T::one() / (T::one() + T::one());

let (sy, cy) = Float::sin_cos(half * yaw.get());
Expand Down Expand Up @@ -532,9 +627,9 @@ fn simple_rotation_2d() {
fn simple_rotation_3d_in_2d() {
use std::f32::consts::{PI, FRAC_PI_2};
let ri = Rotation3D::identity();
let r90 = Rotation3D::around_z(Radians::new(FRAC_PI_2));
let rm90 = Rotation3D::around_z(Radians::new(-FRAC_PI_2));
let r180 = Rotation3D::around_z(Radians::new(PI));
let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2));
let r180 = Rotation3D::around_z(Angle::radians(PI));

assert!(ri.rotate_point2d(&point2(1.0, 2.0)).approx_eq(&point2(1.0, 2.0)));
assert!(r90.rotate_point2d(&point2(1.0, 2.0)).approx_eq(&point2(-2.0, 1.0)));
Expand All @@ -551,9 +646,9 @@ fn simple_rotation_3d_in_2d() {
#[test]
fn pre_post() {
use std::f32::consts::{FRAC_PI_2};
let r1 = Rotation3D::around_x(Radians::new(FRAC_PI_2));
let r2 = Rotation3D::around_y(Radians::new(FRAC_PI_2));
let r3 = Rotation3D::around_z(Radians::new(FRAC_PI_2));
let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2));
let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2));
let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));

let t1 = r1.to_transform();
let t2 = r2.to_transform();
Expand All @@ -578,15 +673,15 @@ fn to_transform3d() {
use std::f32::consts::{PI, FRAC_PI_2};
let rotations = [
Rotation3D::identity(),
Rotation3D::around_x(Radians::new(FRAC_PI_2)),
Rotation3D::around_x(Radians::new(-FRAC_PI_2)),
Rotation3D::around_x(Radians::new(PI)),
Rotation3D::around_y(Radians::new(FRAC_PI_2)),
Rotation3D::around_y(Radians::new(-FRAC_PI_2)),
Rotation3D::around_y(Radians::new(PI)),
Rotation3D::around_z(Radians::new(FRAC_PI_2)),
Rotation3D::around_z(Radians::new(-FRAC_PI_2)),
Rotation3D::around_z(Radians::new(PI)),
Rotation3D::around_x(Angle::radians(FRAC_PI_2)),
Rotation3D::around_x(Angle::radians(-FRAC_PI_2)),
Rotation3D::around_x(Angle::radians(PI)),
Rotation3D::around_y(Angle::radians(FRAC_PI_2)),
Rotation3D::around_y(Angle::radians(-FRAC_PI_2)),
Rotation3D::around_y(Angle::radians(PI)),
Rotation3D::around_z(Angle::radians(FRAC_PI_2)),
Rotation3D::around_z(Angle::radians(-FRAC_PI_2)),
Rotation3D::around_z(Angle::radians(PI)),
];

let points = [
Expand Down Expand Up @@ -638,13 +733,13 @@ fn around_axis() {
use std::f32::consts::{PI, FRAC_PI_2};

// Two sort of trivial cases:
let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Radians::new(PI));
let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Radians::new(FRAC_PI_2));
let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI));
let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2));
assert!(r1.rotate_point3d(&point3(1.0, 2.0, 0.0)).approx_eq(&point3(2.0, 1.0, 0.0)));
assert!(r2.rotate_point3d(&point3(1.0, 0.0, 0.0)).approx_eq(&point3(0.5, 0.5, -0.5.sqrt())));

// A more arbitray test (made up with numpy):
let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Radians::new(2.291288));
let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288));
assert!(r3.rotate_point3d(&point3(1.0, 0.0, 0.0)).approx_eq(&point3(-0.58071821, 0.81401868, -0.01182979)));
}

Expand All @@ -658,8 +753,8 @@ fn from_euler() {
// of transforming a point rather than the values of each qauetrnions.
let p = point3(1.0, 2.0, 3.0);

let angle = Radians::new(FRAC_PI_2);
let zero = Radians::new(0.0);
let angle = Angle::radians(FRAC_PI_2);
let zero = Angle::radians(0.0);

// roll
let roll_re = Rotation3D::euler(angle, zero, zero);
Expand Down
12 changes: 6 additions & 6 deletions src/transform2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use super::{UnknownUnit, Radians};
use super::{UnknownUnit, Angle};
use num::{One, Zero};
use point::TypedPoint2D;
use vector::{TypedVector2D, vec2};
Expand Down Expand Up @@ -249,7 +249,7 @@ where T: Copy + Clone +
}

/// Returns a rotation transform.
pub fn create_rotation(theta: Radians<T>) -> Self {
pub fn create_rotation(theta: Angle<T>) -> Self {
let _0 = Zero::zero();
let cos = theta.get().cos();
let sin = theta.get().sin();
Expand All @@ -262,13 +262,13 @@ where T: Copy + Clone +

/// Applies a rotation after self's transformation and returns the resulting transform.
#[cfg_attr(feature = "unstable", must_use)]
pub fn post_rotate(&self, theta: Radians<T>) -> Self {
pub fn post_rotate(&self, theta: Angle<T>) -> Self {
self.post_mul(&TypedTransform2D::create_rotation(theta))
}

/// Applies a rotation after self's transformation and returns the resulting transform.
#[cfg_attr(feature = "unstable", must_use)]
pub fn pre_rotate(&self, theta: Radians<T>) -> Self {
pub fn pre_rotate(&self, theta: Angle<T>) -> Self {
self.pre_mul(&TypedTransform2D::create_rotation(theta))
}

Expand Down Expand Up @@ -394,13 +394,13 @@ mod test {
use super::*;
use approxeq::ApproxEq;
use point::Point2D;
use Radians;
use Angle;

use std::f32::consts::FRAC_PI_2;

type Mat = Transform2D<f32>;

fn rad(v: f32) -> Radians<f32> { Radians::new(v) }
fn rad(v: f32) -> Angle<f32> { Angle::radians(v) }

#[test]
pub fn test_translation() {
Expand Down
Loading

0 comments on commit 58904ae

Please sign in to comment.