use alga::general::RealField;
use num_traits::Float;
use {BoundingBox, Object, PrimitiveParameters};
#[derive(Clone, Debug)]
pub struct AffineTransformer<S: RealField> {
object: Box<dyn Object<S>>,
transform: na::Matrix4<S>,
transposed3x3: na::Matrix3<S>,
scale_min: S,
bbox: BoundingBox<S>,
}
impl<S: RealField + Float + From<f32>> Object<S> for AffineTransformer<S> {
fn approx_value(&self, p: &na::Point3<S>, slack: S) -> S {
let approx = self.bbox.distance(p);
if approx <= slack {
self.object
.approx_value(&self.transform.transform_point(&p), slack / self.scale_min)
* self.scale_min
} else {
approx
}
}
fn bbox(&self) -> &BoundingBox<S> {
&self.bbox
}
fn set_parameters(&mut self, p: &PrimitiveParameters<S>) {
self.object.set_parameters(p);
}
fn normal(&self, p: &na::Point3<S>) -> na::Vector3<S> {
let normal_at_p = self.object.normal(&self.transform.transform_point(&p));
let transformed_normal = self.transposed3x3 * normal_at_p;
transformed_normal.normalize()
}
fn translate(&self, v: &na::Vector3<S>) -> Box<dyn Object<S>> {
let new_trans = self.transform.prepend_translation(&-v);
Box::new(AffineTransformer::new_with_scaler(
self.object.clone(),
new_trans,
self.scale_min,
))
}
fn rotate(&self, r: &na::Vector3<S>) -> Box<dyn Object<S>> {
let euler = ::na::Rotation::from_euler_angles(r.x, r.y, r.z).to_homogeneous();
let new_trans = self.transform * euler;
Box::new(AffineTransformer::new_with_scaler(
self.object.clone(),
new_trans,
self.scale_min,
))
}
fn scale(&self, s: &na::Vector3<S>) -> Box<dyn Object<S>> {
let one: S = From::from(1f32);
let new_trans = self.transform.prepend_nonuniform_scaling(&na::Vector3::new(
one / s.x,
one / s.y,
one / s.z,
));
Box::new(AffineTransformer::new_with_scaler(
self.object.clone(),
new_trans,
self.scale_min * Float::min(s.x, Float::min(s.y, s.z)),
))
}
}
impl<S: RealField + Float + From<f32>> AffineTransformer<S> {
fn identity(o: Box<dyn Object<S>>) -> Self {
AffineTransformer::new(o, na::Matrix4::identity())
}
fn new(o: Box<dyn Object<S>>, t: na::Matrix4<S>) -> Self {
let one: S = From::from(1f32);
AffineTransformer::new_with_scaler(o, t, one)
}
fn new_with_scaler(o: Box<dyn Object<S>>, t: na::Matrix4<S>, scale_min: S) -> Self {
match t.try_inverse() {
None => panic!("Failed to invert {:?}", t),
Some(t_inv) => {
let bbox = o.bbox().transform(&t_inv);
let transposed3x3 = t
.fixed_slice::<::na::core::dimension::U3, ::na::core::dimension::U3>(0, 0)
.transpose();
AffineTransformer {
object: o,
transform: t,
transposed3x3,
scale_min,
bbox,
}
}
}
}
pub fn new_translate(o: Box<dyn Object<S>>, v: &na::Vector3<S>) -> Box<dyn Object<S>> {
AffineTransformer::identity(o).translate(v)
}
pub fn new_rotate(o: Box<dyn Object<S>>, r: &na::Vector3<S>) -> Box<dyn Object<S>> {
AffineTransformer::identity(o).rotate(r)
}
pub fn new_scale(o: Box<dyn Object<S>>, s: &na::Vector3<S>) -> Box<dyn Object<S>> {
AffineTransformer::identity(o).scale(s)
}
}
#[cfg(test)]
mod test {
use super::super::test::MockObject;
use super::*;
#[test]
fn translate() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let translation = na::Vector3::new(0.0001, 0.0, 0.0);
let translated = mock_object.translate(&translation);
let p = na::Point3::new(1.0, 0.0, 0.0);
assert_eq!(translated.normal(&p), normal);
assert_eq!(receiver.recv().unwrap(), p - translation);
}
#[test]
fn scale() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let scale = na::Vector3::new(0.1, 0.1, 0.1);
let scaled = mock_object.scale(&scale);
let p = na::Point3::new(1.0, 0.0, 0.0);
assert_eq!(scaled.normal(&p), normal);
assert_eq!(receiver.recv().unwrap(), p / 0.1);
}
#[test]
fn rotate() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let rotation = na::Vector3::new(0.0, 0.0, ::std::f64::consts::PI / 6.0);
let rotated = mock_object.rotate(&rotation);
let p = na::Point3::new(1.0, 0.0, 0.0);
assert_relative_eq!(
rotated.normal(&p),
na::Vector3::new(num_traits::Float::sqrt(3.0) / 2.0, -0.5, 0.0)
);
assert_relative_eq!(
receiver.try_recv().unwrap(),
na::Point3::new(num_traits::Float::sqrt(3.0) / 2.0, 0.5, 0.0)
);
}
#[test]
fn scale_and_translate() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let scale = na::Vector3::new(0.1, 0.1, 0.1);
let scaled = mock_object.scale(&scale);
let translation = na::Vector3::new(5.0, 0.0, 0.0);
let translated = scaled.translate(&translation);
let p = na::Point3::new(1.0, 0.0, 0.0);
assert_eq!(translated.normal(&p), normal);
assert_eq!(receiver.recv().unwrap(), (p - translation) / 0.1);
}
#[test]
fn translate_and_scale() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let translation = na::Vector3::new(5.0, 0.0, 0.0);
let translated = mock_object.translate(&translation);
let scale = na::Vector3::new(0.1, 0.1, 0.1);
let scaled = translated.scale(&scale);
let p = na::Point3::new(1.0, 0.0, 0.0);
assert_eq!(scaled.normal(&p), normal);
assert_eq!(receiver.recv().unwrap(), p / 0.1 - translation);
}
#[test]
fn rotate_and_translate() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let rotation = na::Vector3::new(0.0, 0.0, ::std::f64::consts::PI / 2.0);
let rotated = mock_object.rotate(&rotation);
let translation = na::Vector3::new(5.0, 0.0, 0.0);
let translated = rotated.translate(&translation);
let p = na::Point3::new(1.0, 0.0, 0.0);
translated.normal(&p);
assert_relative_eq!(
receiver.recv().unwrap(),
na::Point3::new(
p.y - translation.y,
p.x - translation.x,
p.z - translation.z
),
epsilon = 10e-10
);
}
#[test]
fn translate_and_rotate() {
let normal = na::Vector3::new(1.0, 0.0, 0.0);
let mut mock_object = MockObject::new(1.0, normal);
let receiver = mock_object.add_normal_call_recorder(1);
let translation = na::Vector3::new(5.0, 0.0, 0.0);
let translated = mock_object.translate(&translation);
let rotation = na::Vector3::new(0.0, 0.0, ::std::f64::consts::PI / 2.0);
let rotated = translated.rotate(&rotation);
let p = na::Point3::new(1.0, 0.0, 0.0);
rotated.normal(&p);
assert_relative_eq!(
receiver.recv().unwrap(),
na::Point3::new(p.y, p.x, p.z) - translation,
epsilon = 10e-10
);
}
}