diff --git a/.travis.yml b/.travis.yml index 7033ead..0b377b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,8 @@ rust: env: global: - secure: afYeHpC8BJeyajd3vd84xRBYJKxQorKfh4lGvesu/hqh0bHc6lxrqGt0Pthb2m2YDODZiGSbx9yr9UetelZIJUSA3LoiG5PdDrtH/Kwdn5xGcBWSZl3v/sH+oB3AeR7Eji6mj0mrydQxn8PQbEACvR6lpGCYE2R48KrftYmD6Js= + - TRAVIS_CARGO_NIGHTLY_FEATURE="eders" + - secure: afYeHpC8BJeyajd3vd84xRBYJKxQorKfh4lGvesu/hqh0bHc6lxrqGt0Pthb2m2YDODZiGSbx9yr9UetelZIJUSA3LoiG5PdDrtH/Kwdn5xGcBWSZl3v/sH+oB3AeR7Eji6mj0mrydQxn8PQbEACvR6lpGCYE2R48KrftYmD6Js= before_script: - | diff --git a/Cargo.toml b/Cargo.toml index 5f02dbf..41ab5e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,16 @@ name = "cgmath" [features] unstable = [] +default = ["rustc-serialize"] +eders = ["serde", "serde_macros"] [dependencies] num-traits = "0.1" rand = "0.3" -rustc-serialize = "0.3" +rustc-serialize = { version = "0.3", optional = true } +serde = { version = "0.7", optional = true } +serde_macros = { version = "0.7", optional = true } [dev-dependencies] glium = "0.14.0" +serde_json = "0.7" diff --git a/src/angle.rs b/src/angle.rs index 7ee16b7..c434129 100644 --- a/src/angle.rs +++ b/src/angle.rs @@ -32,14 +32,18 @@ use num::BaseFloat; /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, PartialEq, PartialOrd, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Rad { pub s: S } /// An angle, in degrees. /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, PartialEq, PartialOrd, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Deg { pub s: S } /// Create a new angle, in radians diff --git a/src/euler.rs b/src/euler.rs index ec28b45..df72875 100644 --- a/src/euler.rs +++ b/src/euler.rs @@ -64,7 +64,8 @@ use num::BaseFloat; #[repr(C, packed)] #[derive(Copy, Clone, Debug)] #[derive(PartialEq, Eq)] -#[derive(RustcEncodable, RustcDecodable)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Euler { /// The angle to apply around the _x_ axis. Also known at the _pitch_. pub x: A, diff --git a/src/lib.rs b/src/lib.rs index 58ee237..2efb293 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,10 +50,18 @@ //! use cgmath::prelude::*; //! ``` +#![cfg_attr(feature = "eders", feature(plugin, custom_derive))] +#![cfg_attr(feature = "eders", plugin(serde_macros))] + pub extern crate num_traits; -extern crate rustc_serialize; extern crate rand; +#[cfg(feature = "rustc-serialize")] +extern crate rustc_serialize; + +#[cfg(feature = "eders")] +extern crate serde; + // Re-exports pub use approx::*; diff --git a/src/matrix.rs b/src/matrix.rs index 8b98632..7336783 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -35,7 +35,9 @@ use vector::{Vector2, Vector3, Vector4}; /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Matrix2 { /// The first column of the matrix. pub x: Vector2, @@ -47,7 +49,9 @@ pub struct Matrix2 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Matrix3 { /// The first column of the matrix. pub x: Vector3, @@ -61,7 +65,9 @@ pub struct Matrix3 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Matrix4 { /// The first column of the matrix. pub x: Vector4, diff --git a/src/point.rs b/src/point.rs index 4a808a2..8ee8393 100644 --- a/src/point.rs +++ b/src/point.rs @@ -31,7 +31,9 @@ use vector::{Vector1, Vector2, Vector3, Vector4}; /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Point1 { pub x: S, } @@ -40,7 +42,9 @@ pub struct Point1 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Point2 { pub x: S, pub y: S, @@ -50,7 +54,9 @@ pub struct Point2 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Point3 { pub x: S, pub y: S, diff --git a/src/projection.rs b/src/projection.rs index 5bbac10..d650aa6 100644 --- a/src/projection.rs +++ b/src/projection.rs @@ -66,7 +66,9 @@ pub fn ortho(left: S, right: S, bottom: S, top: S, near: S, far: S } /// A perspective projection based on a vertical field-of-view angle. -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct PerspectiveFov { pub fovy: Rad, pub aspect: S, @@ -132,7 +134,9 @@ impl From> for Matrix4 { } /// A perspective projection with arbitrary left/right/bottom/top distances -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Perspective { pub left: S, pub right: S, @@ -178,7 +182,9 @@ impl From> for Matrix4 { } /// An orthographic projection with arbitrary left/right/bottom/top distances -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Ortho { pub left: S, pub right: S, diff --git a/src/quaternion.rs b/src/quaternion.rs index 1b5a1ff..7556330 100644 --- a/src/quaternion.rs +++ b/src/quaternion.rs @@ -36,7 +36,9 @@ use vector::Vector3; /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Quaternion { /// The scalar part of the quaternion. pub s: S, diff --git a/src/rotation.rs b/src/rotation.rs index d819de0..5b570c4 100644 --- a/src/rotation.rs +++ b/src/rotation.rs @@ -137,7 +137,9 @@ pub trait Rotation3: Rotation> /// let unit_y3 = (rot_half * rot_half).rotate_vector(unit_x); /// assert!(unit_y3.approx_eq(&unit_y2)); /// ``` -#[derive(PartialEq, Copy, Clone, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Copy, Clone)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Basis2 { mat: Matrix2 } @@ -209,7 +211,9 @@ impl fmt::Debug for Basis2 { /// inversion, can be implemented more efficiently than the implementations for /// `math::Matrix3`. To ensure orthogonality is maintained, the operations have /// been restricted to a subeset of those implemented on `Matrix3`. -#[derive(PartialEq, Copy, Clone, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Copy, Clone)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Basis3 { mat: Matrix3 } diff --git a/src/transform.rs b/src/transform.rs index 9ad25e8..284d647 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -56,7 +56,8 @@ pub trait Transform: Sized { /// A generic transformation consisting of a rotation, /// displacement vector and scale amount. -#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] pub struct Decomposed { pub scale: V::Scalar, pub rot: R, @@ -147,3 +148,162 @@ impl> From, R>> for Matrix4< impl> Transform2 for Decomposed, R> {} impl> Transform3 for Decomposed, R> {} + +impl ApproxEq for Decomposed + where S: ApproxEq, S::Scalar: ApproxEq, R: ApproxEq +{ + type Epsilon = E; + + fn approx_eq_eps(&self, other: &Self, epsilon: &Self::Epsilon) -> bool { + self.scale.approx_eq_eps(&other.scale, epsilon) && + self.rot.approx_eq_eps(&other.rot, epsilon) && + self.disp.approx_eq_eps(&other.disp, epsilon) + } +} + +#[cfg(feature = "eders")] +#[doc(hidden)] +mod eders_ser { + use structure::VectorSpace; + use super::Decomposed; + use serde::{self, Serialize}; + + impl Serialize for Decomposed + where V: Serialize, V::Scalar: Serialize, R: Serialize + { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: serde::Serializer + { + serializer.serialize_struct("Decomposed", DecomposedVisitor { + value: self, + state: 0, + }) + } + } + + struct DecomposedVisitor<'a, V: 'a + VectorSpace, R: 'a> { + value: &'a Decomposed, + state: u8, + } + + impl<'a, V: 'a + VectorSpace, R> serde::ser::MapVisitor for DecomposedVisitor<'a, V, R> + where V: Serialize, V::Scalar: Serialize, R: Serialize + { + fn visit(&mut self, serializer: &mut S) -> Result, S::Error> + where S: serde::Serializer + { + match self.state { + 0 => { + self.state += 1; + Ok(Some(try!(serializer.serialize_struct_elt("scale", &self.value.scale)))) + }, + 1 => { + self.state += 1; + Ok(Some(try!(serializer.serialize_struct_elt("rot", &self.value.rot)))) + }, + 2 => { + self.state += 1; + Ok(Some(try!(serializer.serialize_struct_elt("disp", &self.value.disp)))) + }, + _ => { + Ok(None) + }, + } + } + } +} + +#[cfg(feature = "eders")] +#[doc(hidden)] +mod eders_de { + use structure::VectorSpace; + use super::Decomposed; + use serde::{self, Deserialize}; + use std::marker::PhantomData; + + enum DecomposedField { + Scale, + Rot, + Disp, + } + + impl Deserialize for DecomposedField { + fn deserialize(deserializer: &mut D) -> Result + where D: serde::Deserializer + { + struct DecomposedFieldVisitor; + + impl serde::de::Visitor for DecomposedFieldVisitor { + type Value = DecomposedField; + + fn visit_str(&mut self, value: &str) -> Result + where E: serde::de::Error + { + match value { + "scale" => Ok(DecomposedField::Scale), + "rot" => Ok(DecomposedField::Rot), + "disp" => Ok(DecomposedField::Disp), + _ => Err(serde::de::Error::custom("expected scale, rot or disp")), + } + } + } + + deserializer.deserialize(DecomposedFieldVisitor) + } + } + + impl Deserialize for Decomposed + where S: Deserialize, S::Scalar: Deserialize, R: Deserialize + { + fn deserialize(deserializer: &mut D) -> Result, D::Error> + where D: serde::de::Deserializer + { + const FIELDS: &'static [&'static str] = &["scale", "rot", "disp"]; + deserializer.deserialize_struct("Decomposed", FIELDS, DecomposedVisitor(PhantomData)) + } + } + + struct DecomposedVisitor(PhantomData<(S, R)>); + + impl serde::de::Visitor for DecomposedVisitor + where S: Deserialize, S::Scalar: Deserialize, R: Deserialize + { + type Value = Decomposed; + + fn visit_map(&mut self, mut visitor: V) -> Result, V::Error> + where V: serde::de::MapVisitor + { + let mut scale = None; + let mut rot = None; + let mut disp = None; + + loop { + match try!(visitor.visit_key()) { + Some(DecomposedField::Scale) => { scale = Some(try!(visitor.visit_value())); }, + Some(DecomposedField::Rot) => { rot = Some(try!(visitor.visit_value())); }, + Some(DecomposedField::Disp) => { disp = Some(try!(visitor.visit_value())); }, + _ => { break; }, + } + } + + let scale = match scale { + Some(scale) => scale, + None => try!(visitor.missing_field("scale")), + }; + + let rot = match rot { + Some(rot) => rot, + None => try!(visitor.missing_field("rot")), + }; + + let disp = match disp { + Some(disp) => disp, + None => try!(visitor.missing_field("disp")), + }; + + try!(visitor.end()); + + Ok(Decomposed { scale: scale, rot: rot, disp: disp }) + } + } +} diff --git a/src/vector.rs b/src/vector.rs index b5ecf03..d3c7997 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -29,7 +29,9 @@ use num::{BaseNum, BaseFloat, PartialOrd}; /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Vector1 { /// The x component of the vector. pub x: S, @@ -39,7 +41,9 @@ pub struct Vector1 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Vector2 { /// The x component of the vector. pub x: S, @@ -51,7 +55,9 @@ pub struct Vector2 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Vector3 { /// The x component of the vector. pub x: S, @@ -65,7 +71,9 @@ pub struct Vector3 { /// /// This type is marked as `#[repr(C, packed)]`. #[repr(C, packed)] -#[derive(PartialEq, Eq, Copy, Clone, Hash, RustcEncodable, RustcDecodable)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] +#[cfg_attr(feature = "eders", derive(Serialize, Deserialize))] pub struct Vector4 { /// The x component of the vector. pub x: S, diff --git a/tests/transform.rs b/tests/transform.rs index e83372f..1688c69 100644 --- a/tests/transform.rs +++ b/tests/transform.rs @@ -16,28 +16,46 @@ extern crate cgmath; +#[cfg(feature = "eders")] +extern crate serde_json; + use cgmath::*; #[test] fn test_invert() { - let v = Vector3::new(1.0f64, 2.0, 3.0); - let t = Decomposed { + let v = Vector3::new(1.0f64, 2.0, 3.0); + let t = Decomposed { scale: 1.5f64, - rot: Quaternion::new(0.5f64,0.5,0.5,0.5), - disp: Vector3::new(6.0f64,-7.0,8.0) + rot: Quaternion::new(0.5f64, 0.5, 0.5, 0.5), + disp: Vector3::new(6.0f64, -7.0, 8.0), }; - let ti = t.inverse_transform().expect("Expected successful inversion"); - let vt = t.transform_vector(v); + let ti = t.inverse_transform().expect("Expected successful inversion"); + let vt = t.transform_vector(v); assert!(v.approx_eq(&ti.transform_vector(vt))); } #[test] fn test_look_at() { - let eye = Point3::new(0.0f64, 0.0, -5.0); - let center = Point3::new(0.0f64, 0.0, 0.0); - let up = Vector3::new(1.0f64, 0.0, 0.0); - let t: Decomposed, Quaternion> = Transform::look_at(eye, center, up); - let point = Point3::new(1.0f64, 0.0, 0.0); - let view_point = Point3::new(0.0f64, 1.0, 5.0); - assert!(t.transform_point(point).approx_eq(&view_point)); + let eye = Point3::new(0.0f64, 0.0, -5.0); + let center = Point3::new(0.0f64, 0.0, 0.0); + let up = Vector3::new(1.0f64, 0.0, 0.0); + let t: Decomposed, Quaternion> = Transform::look_at(eye, center, up); + let point = Point3::new(1.0f64, 0.0, 0.0); + let view_point = Point3::new(0.0f64, 1.0, 5.0); + assert!(t.transform_point(point).approx_eq(&view_point)); +} + +#[cfg(feature = "eders")] +#[test] +fn test_serialize() { + let t = Decomposed { + scale: 1.5f64, + rot: Quaternion::new(0.5f64, 0.5, 0.5, 0.5), + disp: Vector3::new(6.0f64, -7.0, 8.0), + }; + + let serialized = serde_json::to_string(&t).unwrap(); + let deserialized: Decomposed, Quaternion> = serde_json::from_str(&serialized).unwrap(); + + assert!(t.approx_eq(&deserialized)); }