<commit_before>use rand::Rng;
use multi_objective::MultiObjective;
use domination::Domination;
use sort::FastNonDominatedSorter;
use crowding_distance::{crowding_distance_assignment, CrowdingDistanceAssignment};
use std::cmp::{self, Ordering};

pub trait SelectSolutions<T, F> : Sized
    where T: CrowdingDistanceAssignment<F>, F: MultiObjective + Domination,
{
    /// Select `n` out of the `solutions`, assigning rank and distance using the first `num_objectives`
    /// objectives. `rng` might not be used.
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           rng: &mut R)
        where R: Rng;
}

/// Uses the approach NSGA takes.
pub struct SelectNSGA;

impl<T, F> SelectSolutions<T, F> for SelectNSGA
    where T: CrowdingDistanceAssignment<F>,
          F: MultiObjective + Domination
{
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           _rng: &mut R)
        where R: Rng
    {
        // We can select at most `select_n` solutions.
        let mut missing = cmp::min(solutions.len(), select_n);

        // make sure all solutions are unselected
        for s in solutions.iter_mut() {
            s.unselect();
        }

        let pareto_fronts = FastNonDominatedSorter::new(solutions, &|s| s.fitness());

        for (rank, mut front) in pareto_fronts.enumerate() {
            assert!(missing > 0);

            // assign rank and crowding distance of those solutions in `front`.
            let _ = crowding_distance_assignment(solutions,
                                                 &mut front,
                                                 rank as u32,
                                                 num_objectives);

            if front.len() <= missing {
                // whole front fits into result. XXX: should we sort?

                for i in front.into_iter() {
                    solutions[i].select();
                    assert!(missing > 0);
                    missing -= 1;
                }

                if missing == 0 {
                    break;
                }
            } else {
                // choose only best from this front, according to the crowding distance.
                front.sort_by(|&a, &b| {
                    debug_assert!(solutions[a].rank() == solutions[b].rank());
                    if solutions[a].has_better_rank_and_crowding(&solutions[b]) {
                        Ordering::Less
                    } else if solutions[b].has_better_rank_and_crowding(&solutions[a]) {
                        Ordering::Greater
                    } else {
                        Ordering::Equal
                    }
                });

                for i in front.into_iter().take(missing) {
                    assert!(missing > 0);
                    solutions[i].select();
                    missing -= 1;
                }

                assert!(missing == 0);
                break;
            }
        }
    }
}

/// Selection using NSGP as described in [1].
///
/// [1]: Multi-objective genetic programming with redundancy-regulations for automatic construction of image feature extractors.
///      Watchareeruetai, Ukrit and Matsumoto, Tetsuya and Takeuchi, Yoshinori and Hiroaki, KUDO and Ohnishi, Noboru.
///      2010
pub struct SelectNSGP {
    pub objective_eps: f64,
}

impl<T, F> SelectSolutions<T, F> for SelectNSGP
    where T: CrowdingDistanceAssignment<F>,
          F: MultiObjective + Domination
{
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           rng: &mut R)
        where R: Rng
    {
        // We can select at most `select_n` solutions.
        let mut missing = cmp::min(solutions.len(), select_n);

        // make sure all solutions are unselected
        for s in solutions.iter_mut() {
            s.unselect();
        }

        let pareto_fronts = FastNonDominatedSorter::new(solutions, &|s| s.fitness());

        let mut fronts_grouped: Vec<Vec<Vec<_>>> = Vec::new();
        for (rank, mut front) in pareto_fronts.enumerate() {

            // first assign rank and crowding distance of those solutions in `front`.
            let min_max_distances = crowding_distance_assignment(solutions,
                                                                 &mut front,
                                                                 rank as u32,
                                                                 num_objectives);

            // then group this front into individuals with similar (or equal) fitness.
            let mut groups: Vec<Vec<usize>> = Vec::new();

            'group: for idx in front {
                // insert `idx` into a group.
                // test fitness of `idx` (solutions[idx]) against a member of each group.
                // if none fits, create a new group.

                let fitness = solutions[idx].fitness();
                for grp in groups.iter_mut() {
                    let cmp_idx = grp[0];

                    if fitness.similar_to(solutions[cmp_idx].fitness(),
                                          &min_max_distances,
                                          self.objective_eps) {
                        grp.push(idx);
                        continue 'group;
                    }
                }

                // create a new group
                groups.push(vec![idx]);
            }

            // update the crowding_distance within each group. simply take the average.
            // also record the number of points in the group within each individual's fitness. this
            // is required for sexual selection.
            for grp in groups.iter() {
                // use same average crowding distance for each point in the group
                assert!(grp.len() > 0);
                let avg: f64 = grp.iter().map(|&idx| solutions[idx].dist()).sum::<f64>() /
                               grp.len() as f64;
                for &idx in grp.iter() {
                    solutions[idx].set_dist(avg);
                    solutions[idx].set_crowd(grp.len());
                }
            }

            fronts_grouped.push(groups);
        }

        // Each pareto_front is now grouped into individuals of same (or similar) fitness.
        // Now go through each front, and in each front, choose exactly one individual
        // from each group. Then go to the next front etc. once we are through all
        // fronts, continue with the first and so on, until enough individuals are selected.

        'outer: while missing > 0 {
            for current_front in fronts_grouped.iter_mut() {
                // select up to `missing` solutions from current_front, but from each group only one
                // (which is choosen randomly).
                for grp in current_front.iter_mut() {
                    if missing > 0 {
                        let i = rng.gen_range(0, grp.len());
                        let idx = grp.swap_remove(i); // remove `i`th element from grp returning the solution index
                        assert!(solutions[idx].is_selected() == false);
                        solutions[idx].select();
                        missing -= 1;
                    } else {
                        break 'outer;
                    }
                }

                // only retain groups which are non-empty
                current_front.retain(|grp| grp.len() > 0);
            }
        }
    }
}

/// Select the best individual out of `k` randomly choosen.
/// This gives individuals with better fitness a higher chance to reproduce.
/// `n` is the total number of individuals.
///
/// NOTE: We are not using `sample(rng, 0..n, k)` as it is *very* expensive.
/// Instead we call `rng.gen_range()` k-times. The drawn items could be the same,
/// but the probability is very low if `n` is high compared to `k`.
#[inline]
pub fn tournament_selection_fast<R: Rng, F>(rng: &mut R,
                                            better_than: F,
                                            n: usize,
                                            k: usize)
                                            -> usize
    where F: Fn(usize, usize) -> bool
{
    assert!(n > 0);
    assert!(k > 0);
    assert!(n >= k);

    let mut best: usize = rng.gen_range(0, n);

    for _ in 1..k {
        let i = rng.gen_range(0, n);
        if better_than(i, best) {
            best = i;
        }
    }

    return best;
}
<commit_msg>Special case elitism for NSGP. Make sure we always select the individual with highest fitness of objective #0<commit_after>use rand::Rng;
use multi_objective::MultiObjective;
use domination::Domination;
use sort::FastNonDominatedSorter;
use crowding_distance::{crowding_distance_assignment, CrowdingDistanceAssignment};
use std::cmp::{self, Ordering};

pub trait SelectSolutions<T, F> : Sized
    where T: CrowdingDistanceAssignment<F>, F: MultiObjective + Domination,
{
    /// Select `n` out of the `solutions`, assigning rank and distance using the first `num_objectives`
    /// objectives. `rng` might not be used.
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           rng: &mut R)
        where R: Rng;
}

/// Uses the approach NSGA takes.
pub struct SelectNSGA;

impl<T, F> SelectSolutions<T, F> for SelectNSGA
    where T: CrowdingDistanceAssignment<F>,
          F: MultiObjective + Domination
{
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           _rng: &mut R)
        where R: Rng
    {
        // We can select at most `select_n` solutions.
        let mut missing = cmp::min(solutions.len(), select_n);

        // make sure all solutions are unselected
        for s in solutions.iter_mut() {
            s.unselect();
        }

        let pareto_fronts = FastNonDominatedSorter::new(solutions, &|s| s.fitness());

        for (rank, mut front) in pareto_fronts.enumerate() {
            assert!(missing > 0);

            // assign rank and crowding distance of those solutions in `front`.
            let _ = crowding_distance_assignment(solutions,
                                                 &mut front,
                                                 rank as u32,
                                                 num_objectives);

            if front.len() <= missing {
                // whole front fits into result. XXX: should we sort?

                for i in front.into_iter() {
                    solutions[i].select();
                    assert!(missing > 0);
                    missing -= 1;
                }

                if missing == 0 {
                    break;
                }
            } else {
                // choose only best from this front, according to the crowding distance.
                front.sort_by(|&a, &b| {
                    debug_assert!(solutions[a].rank() == solutions[b].rank());
                    if solutions[a].has_better_rank_and_crowding(&solutions[b]) {
                        Ordering::Less
                    } else if solutions[b].has_better_rank_and_crowding(&solutions[a]) {
                        Ordering::Greater
                    } else {
                        Ordering::Equal
                    }
                });

                for i in front.into_iter().take(missing) {
                    assert!(missing > 0);
                    solutions[i].select();
                    missing -= 1;
                }

                assert!(missing == 0);
                break;
            }
        }
    }
}

/// Selection using NSGP as described in [1].
///
/// [1]: Multi-objective genetic programming with redundancy-regulations for automatic construction of image feature extractors.
///      Watchareeruetai, Ukrit and Matsumoto, Tetsuya and Takeuchi, Yoshinori and Hiroaki, KUDO and Ohnishi, Noboru.
///      2010
pub struct SelectNSGP {
    pub objective_eps: f64,
}

impl<T, F> SelectSolutions<T, F> for SelectNSGP
    where T: CrowdingDistanceAssignment<F>,
          F: MultiObjective + Domination
{
    fn select_solutions<R>(&self,
                           solutions: &mut [T],
                           select_n: usize,
                           num_objectives: usize,
                           rng: &mut R)
        where R: Rng
    {
        // We can select at most `select_n` solutions.
        let mut missing = cmp::min(solutions.len(), select_n);

        // make sure all solutions are unselected
        for s in solutions.iter_mut() {
            s.unselect();
        }

        let pareto_fronts = FastNonDominatedSorter::new(solutions, &|s| s.fitness());

        let mut fronts_grouped: Vec<Vec<Vec<_>>> = Vec::new();
        for (rank, mut front) in pareto_fronts.enumerate() {

            // first assign rank and crowding distance of those solutions in `front`.
            let min_max_distances = crowding_distance_assignment(solutions,
                                                                 &mut front,
                                                                 rank as u32,
                                                                 num_objectives);

            // then group this front into individuals with similar (or equal) fitness.
            let mut groups: Vec<Vec<usize>> = Vec::new();

            'group: for idx in front {
                // insert `idx` into a group.
                // test fitness of `idx` (solutions[idx]) against a member of each group.
                // if none fits, create a new group.

                let fitness = solutions[idx].fitness();
                for grp in groups.iter_mut() {
                    let cmp_idx = grp[0];

                    if fitness.similar_to(solutions[cmp_idx].fitness(),
                                          &min_max_distances,
                                          self.objective_eps) {
                        grp.push(idx);
                        continue 'group;
                    }
                }

                // create a new group
                groups.push(vec![idx]);
            }

            // update the crowding_distance within each group. simply take the average.
            // also record the number of points in the group within each individual's fitness. this
            // is required for sexual selection.
            for grp in groups.iter_mut() {
                // use same average crowding distance for each point in the group
                assert!(grp.len() > 0);
                let avg: f64 = grp.iter().map(|&idx| solutions[idx].dist()).sum::<f64>() /
                               grp.len() as f64;
                for &idx in grp.iter() {
                    solutions[idx].set_dist(avg);
                    solutions[idx].set_crowd(grp.len());
                }

                // sort grp according to primary fitness
                grp.sort_by(|&a, &b| {
                    solutions[a].fitness().cmp_objective(solutions[b].fitness(), 0)
                });
            }

            // group fronts, so that the first group is the one which contains
            // the best individual of fitness objective #0. this makes
            // sure that we always ever include the elite
            groups.sort_by(|a, b| {
                solutions[a[0]].fitness().cmp_objective(solutions[b[0]].fitness(), 0)
            });

            fronts_grouped.push(groups);
        }

        // Each pareto_front has now been grouped into individuals of same (or similar) fitness.
        // Now go through each front, and in each front, choose exactly one individual
        // from each group. Then go to the next front etc. once we are through all
        // fronts, continue with the first and so on, until enough individuals are selected.

        let mut round = 0;
        'outer: while missing > 0 {
            for (front_i, current_front) in fronts_grouped.iter_mut().enumerate() {
                // select up to `missing` solutions from current_front, but from each group only one
                // (which is choosen randomly).
                for (grp_i, grp) in current_front.iter_mut().enumerate() {
                    if missing > 0 {
                        let i =
                        if round == 0 && front_i == 0 && grp_i == 0{
                            // first round, first group and non-dominated front. Use always the best according
                            // to 0th objective.
                            0
                        } else {
                            // Select randomly
                            rng.gen_range(0, grp.len())
                        };
                        let idx = grp.swap_remove(i); // remove `i`th element from grp returning the solution index
                        assert!(solutions[idx].is_selected() == false);
                        solutions[idx].select();
                        missing -= 1;
                    } else {
                        break 'outer;
                    }
                }

                // only retain groups which are non-empty
                current_front.retain(|grp| grp.len() > 0);
            }
            round += 1;
        }
    }
}

/// Select the best individual out of `k` randomly choosen.
/// This gives individuals with better fitness a higher chance to reproduce.
/// `n` is the total number of individuals.
///
/// NOTE: We are not using `sample(rng, 0..n, k)` as it is *very* expensive.
/// Instead we call `rng.gen_range()` k-times. The drawn items could be the same,
/// but the probability is very low if `n` is high compared to `k`.
#[inline]
pub fn tournament_selection_fast<R: Rng, F>(rng: &mut R,
                                            better_than: F,
                                            n: usize,
                                            k: usize)
                                            -> usize
    where F: Fn(usize, usize) -> bool
{
    assert!(n > 0);
    assert!(k > 0);
    assert!(n >= k);

    let mut best: usize = rng.gen_range(0, n);

    for _ in 1..k {
        let i = rng.gen_range(0, n);
        if better_than(i, best) {
            best = i;
        }
    }

    return best;
}
<|endoftext|>


<commit_before>#![allow(dead_code, unused_imports)]

extern crate fitsio_sys as sys;
extern crate libc;

use std::ptr;
use std::ffi;

mod stringutils;

/// Error type
#[derive(Debug, PartialEq, Eq)]
pub struct FitsError {
    status: i32,
    message: String,
}

pub type Result<T> = std::result::Result<T, FitsError>;

/// Hdu description type
///
/// Any way of describing a HDU - number or string which either
/// changes the hdu by absolute number, or by name.
pub trait DescribesHdu {
    fn change_hdu(&self, fptr: &FitsFile) -> Result<()>;
}

impl DescribesHdu for usize {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        unsafe {
            sys::ffmahd(f.fptr, (*self + 1) as i32, &mut _hdu_type, &mut status);
        }
        match status {
            0 => Ok(()),
            _ => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }
}

impl<'a> DescribesHdu for &'a str {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        let c_hdu_name = ffi::CString::new(*self).unwrap();

        unsafe {
            sys::ffmnhd(f.fptr,
                        sys::HduType::ANY_HDU as libc::c_int,
                        c_hdu_name.into_raw(),
                        0,
                        &mut status);
        }

        match status {
            0 => Ok(()),
            _ => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }
}

/// Trait applied to types which can be read from a FITS header
///
/// This is currently:
///
/// * i32
/// * i64
/// * f32
/// * f64
/// * String
pub trait ReadsKey {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> where Self: std::marker::Sized;
}

macro_rules! reads_key_impl {
    ($t:ty, $func:ident) => (
        impl ReadsKey for $t {
            fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;
                let mut value: Self = Self::default();

                unsafe {
                    sys::$func(f.fptr,
                           c_name.into_raw(),
                           &mut value,
                           ptr::null_mut(),
                           &mut status);
                }

                match status {
                    0 => Ok(value),
                    s => {
                        Err(FitsError {
                            status: s,
                            message: stringutils::status_to_string(s).unwrap(),
                        })
                    }
                }
            }
        }
    )
}

reads_key_impl!(i32, ffgkyl);
reads_key_impl!(i64, ffgkyj);
reads_key_impl!(f32, ffgkye);
reads_key_impl!(f64, ffgkyd);

impl ReadsKey for String {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;
        let mut value: Vec<libc::c_char> = vec![0; sys::MAX_VALUE_LENGTH];

        unsafe {
            sys::ffgkys(f.fptr,
                        c_name.into_raw(),
                        value.as_mut_ptr(),
                        ptr::null_mut(),
                        &mut status);
        }

        match status {
            0 => {
                let value: Vec<u8> = value.iter()
                    .map(|&x| x as u8)
                    .filter(|&x| x != 0)
                    .collect();
                Ok(String::from_utf8(value).unwrap())
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }

    }
}

pub struct FitsFile {
    fptr: *mut sys::fitsfile,
    pub filename: String,
}

impl FitsFile {
    pub fn open(filename: &str) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let c_filename = ffi::CString::new(filename).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        sys::FileOpenMode::READONLY as libc::c_int,
                        &mut status);
        }

        match status {
            0 => {
                Ok(FitsFile {
                    fptr: fptr,
                    filename: filename.to_string(),
                })
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }

    }

    pub fn create(path: &str) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let c_filename = ffi::CString::new(path).unwrap();

        unsafe {
            sys::ffinit(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        &mut status);
        }

        match status {
            0 => {
                Ok(FitsFile {
                    fptr: fptr,
                    filename: path.to_string(),
                })
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }

    pub fn hdu<T: DescribesHdu>(&self, hdu_description: T) -> Result<FitsHdu> {
        try!(hdu_description.change_hdu(self));
        let hdu_number = self.hdu_number();

        Ok(FitsHdu {
            fits_file: self,
            hdunum: hdu_number,
        })
    }

    fn hdu_number(&self) -> usize {
        let mut hdu_num = 0;
        unsafe {
            sys::ffghdn(self.fptr, &mut hdu_num);
        }
        (hdu_num - 1) as usize
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        let mut status = 0;
        unsafe {
            sys::ffclos(self.fptr, &mut status);
        }
    }
}

pub struct FitsHdu<'a> {
    fits_file: &'a FitsFile,
    hdunum: usize,
}

impl<'a> FitsHdu<'a> {
    pub fn read_key<T: ReadsKey>(&self, name: &str) -> Result<T> {
        T::read_key(self.fits_file, name)
    }
}

#[cfg(test)]
mod test {
    extern crate tempdir;
    use super::FitsFile;

    #[test]
    fn opening_an_existing_file() {
        match FitsFile::open("../testdata/full_example.fits") {
            Ok(_) => {}
            Err(e) => panic!("{:?}", e),
        }
    }

    #[test]
    fn creating_a_new_file() {
        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");
        assert!(!filename.exists());

        match FitsFile::create(filename.to_str().unwrap()) {
            Ok(_) => assert!(filename.exists()),
            Err(e) => panic!("Error: {:?}", e),
        }
    }

    #[test]
    fn fetching_a_hdu() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        for i in 0..2 {
            assert_eq!(f.hdu(i).unwrap().hdunum, i);
        }
        match f.hdu(2) {
            Err(e) => assert_eq!(e.status, 107),
            _ => panic!("Error checking for failure"),
        }

        let tbl_hdu = f.hdu("TESTEXT").unwrap();
        assert_eq!(tbl_hdu.hdunum, 1);
    }

    #[test]
    fn reading_header_keys() {
        let f = FitsFile::open("../testdata/full_example.fits").unwrap();
        match f.hdu(0).unwrap().read_key::<i64>("INTTEST") {
            Ok(value) => assert_eq!(value, 42),
            Err(e) => panic!("Error reading key: {:?}", e),
        }

        match f.hdu(0).unwrap().read_key::<f64>("DBLTEST") {
            Ok(value) => assert_eq!(value, 0.09375),
            Err(e) => panic!("Error reading key: {:?}", e),
        }

        match f.hdu(0).unwrap().read_key::<String>("TEST") {
            Ok(value) => assert_eq!(value, "value"),
            Err(e) => panic!("Error reading key: {:?}", e),
        }
    }
}
<commit_msg>Remove FitsHdu object and just return self<commit_after>#![allow(dead_code, unused_imports)]

extern crate fitsio_sys as sys;
extern crate libc;

use std::ptr;
use std::ffi;

mod stringutils;

/// Error type
#[derive(Debug, PartialEq, Eq)]
pub struct FitsError {
    status: i32,
    message: String,
}

pub type Result<T> = std::result::Result<T, FitsError>;

/// Hdu description type
///
/// Any way of describing a HDU - number or string which either
/// changes the hdu by absolute number, or by name.
pub trait DescribesHdu {
    fn change_hdu(&self, fptr: &FitsFile) -> Result<()>;
}

impl DescribesHdu for usize {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        unsafe {
            sys::ffmahd(f.fptr, (*self + 1) as i32, &mut _hdu_type, &mut status);
        }
        match status {
            0 => Ok(()),
            _ => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }
}

impl<'a> DescribesHdu for &'a str {
    fn change_hdu(&self, f: &FitsFile) -> Result<()> {
        let mut _hdu_type = 0;
        let mut status = 0;
        let c_hdu_name = ffi::CString::new(*self).unwrap();

        unsafe {
            sys::ffmnhd(f.fptr,
                        sys::HduType::ANY_HDU as libc::c_int,
                        c_hdu_name.into_raw(),
                        0,
                        &mut status);
        }

        match status {
            0 => Ok(()),
            _ => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }
}

/// Trait applied to types which can be read from a FITS header
///
/// This is currently:
///
/// * i32
/// * i64
/// * f32
/// * f64
/// * String
pub trait ReadsKey {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> where Self: std::marker::Sized;
}

macro_rules! reads_key_impl {
    ($t:ty, $func:ident) => (
        impl ReadsKey for $t {
            fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
                let c_name = ffi::CString::new(name).unwrap();
                let mut status = 0;
                let mut value: Self = Self::default();

                unsafe {
                    sys::$func(f.fptr,
                           c_name.into_raw(),
                           &mut value,
                           ptr::null_mut(),
                           &mut status);
                }

                match status {
                    0 => Ok(value),
                    s => {
                        Err(FitsError {
                            status: s,
                            message: stringutils::status_to_string(s).unwrap(),
                        })
                    }
                }
            }
        }
    )
}

reads_key_impl!(i32, ffgkyl);
reads_key_impl!(i64, ffgkyj);
reads_key_impl!(f32, ffgkye);
reads_key_impl!(f64, ffgkyd);

impl ReadsKey for String {
    fn read_key(f: &FitsFile, name: &str) -> Result<Self> {
        let c_name = ffi::CString::new(name).unwrap();
        let mut status = 0;
        let mut value: Vec<libc::c_char> = vec![0; sys::MAX_VALUE_LENGTH];

        unsafe {
            sys::ffgkys(f.fptr,
                        c_name.into_raw(),
                        value.as_mut_ptr(),
                        ptr::null_mut(),
                        &mut status);
        }

        match status {
            0 => {
                let value: Vec<u8> = value.iter()
                    .map(|&x| x as u8)
                    .filter(|&x| x != 0)
                    .collect();
                Ok(String::from_utf8(value).unwrap())
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }

    }
}

pub struct FitsFile {
    fptr: *mut sys::fitsfile,
    pub filename: String,
    hdu_number: usize,
}

impl FitsFile {
    pub fn open(filename: &str) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let c_filename = ffi::CString::new(filename).unwrap();

        unsafe {
            sys::ffopen(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        sys::FileOpenMode::READONLY as libc::c_int,
                        &mut status);
        }

        match status {
            0 => {
                Ok(FitsFile {
                    fptr: fptr,
                    hdu_number: 0,
                    filename: filename.to_string(),
                })
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }

    }

    pub fn create(path: &str) -> Result<Self> {
        let mut fptr = ptr::null_mut();
        let mut status = 0;
        let c_filename = ffi::CString::new(path).unwrap();

        unsafe {
            sys::ffinit(&mut fptr as *mut *mut sys::fitsfile,
                        c_filename.as_ptr(),
                        &mut status);
        }

        match status {
            0 => {
                Ok(FitsFile {
                    fptr: fptr,
                    hdu_number: 0,
                    filename: path.to_string(),
                })
            }
            status => {
                Err(FitsError {
                    status: status,
                    message: stringutils::status_to_string(status).unwrap(),
                })
            }
        }
    }

    pub fn hdu<T: DescribesHdu>(&mut self, hdu_description: T) -> Result<&Self> {
        try!(hdu_description.change_hdu(self));
        self.hdu_number = self.hdu_number();
        Ok(self)
    }

    fn hdu_number(&self) -> usize {
        let mut hdu_num = 0;
        unsafe {
            sys::ffghdn(self.fptr, &mut hdu_num);
        }
        (hdu_num - 1) as usize
    }

    pub fn read_key<T: ReadsKey>(&self, name: &str) -> Result<T> {
        T::read_key(self, name)
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        let mut status = 0;
        unsafe {
            sys::ffclos(self.fptr, &mut status);
        }
    }
}

#[cfg(test)]
mod test {
    extern crate tempdir;
    use super::FitsFile;

    #[test]
    fn opening_an_existing_file() {
        match FitsFile::open("../testdata/full_example.fits") {
            Ok(_) => {}
            Err(e) => panic!("{:?}", e),
        }
    }

    #[test]
    fn creating_a_new_file() {
        let tdir = tempdir::TempDir::new("fitsio-").unwrap();
        let tdir_path = tdir.path();
        let filename = tdir_path.join("test.fits");
        assert!(!filename.exists());

        match FitsFile::create(filename.to_str().unwrap()) {
            Ok(_) => assert!(filename.exists()),
            Err(e) => panic!("Error: {:?}", e),
        }
    }

    #[test]
    fn fetching_a_hdu() {
        let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
        for i in 0..2 {
            assert_eq!(f.hdu(i).unwrap().hdu_number, i);
        }
        match f.hdu(2) {
            Err(e) => assert_eq!(e.status, 107),
            _ => panic!("Error checking for failure"),
        }

        let tbl_hdu = f.hdu("TESTEXT").unwrap();
        assert_eq!(tbl_hdu.hdu_number, 1);
    }

    #[test]
    fn reading_header_keys() {
        let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
        match f.hdu(0).unwrap().read_key::<i64>("INTTEST") {
            Ok(value) => assert_eq!(value, 42),
            Err(e) => panic!("Error reading key: {:?}", e),
        }

        match f.hdu(0).unwrap().read_key::<f64>("DBLTEST") {
            Ok(value) => assert_eq!(value, 0.09375),
            Err(e) => panic!("Error reading key: {:?}", e),
        }

        match f.hdu(0).unwrap().read_key::<String>("TEST") {
            Ok(value) => assert_eq!(value, "value"),
            Err(e) => panic!("Error reading key: {:?}", e),
        }
    }
}
<|endoftext|>


<commit_before>//! An example showcasing animation using `keyframe`.
//! Includes tweening and frame-by-frame animation.
//! Credit for the animation goes to [Dead Revolver](https://deadrevolver.itch.io/pixel-prototype-player-sprites)

#[macro_use]
extern crate num_derive;

use ggez::event;
use ggez::graphics::{self, Color, DrawParam, FilterMode, Rect, Text, TextFragment};
use ggez::input::keyboard::KeyCode;
use ggez::mint::Point2;
use ggez::{Context, GameResult};
use glam::*;
use keyframe::{ease, functions::*, keyframes, AnimationSequence, EasingFunction};
use keyframe_derive::CanTween;
use num_traits::{FromPrimitive, ToPrimitive};
use std::env;
use std::path;

struct MainState {
    spritesheet: graphics::Image,
    easing_enum: EasingEnum,
    animation_type: AnimationType,
    ball_animation: AnimationSequence<Point2<f32>>,
    player_animation: AnimationSequence<TweenableRect>,
    duration: f32,
}

#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq)]
#[repr(i32)]
enum EasingEnum {
    Linear,
    EaseIn,
    EaseInOut,
    EaseOut,
    EaseInCubic,
    EaseOutCubic,
    EaseInOutCubic,
    Bezier,
    EaseInOut3Point,
}

fn easing_function(ease_enum: &EasingEnum) -> Box<dyn EasingFunction + Send + Sync> {
    match ease_enum {
        EasingEnum::Linear => Box::new(Linear),
        EasingEnum::EaseIn => Box::new(EaseIn),
        EasingEnum::EaseInOut => Box::new(EaseInOut),
        EasingEnum::EaseOut => Box::new(EaseOut),
        EasingEnum::EaseInCubic => Box::new(EaseInCubic),
        EasingEnum::EaseOutCubic => Box::new(EaseOutCubic),
        EasingEnum::EaseInOutCubic => Box::new(EaseInOutCubic),
        EasingEnum::Bezier => Box::new(BezierCurve::from([0.6, 0.04].into(), [0.98, 0.335].into())),
        _ => panic!(),
    }
}

fn ball_sequence(ease_enum: &EasingEnum, duration: f32) -> AnimationSequence<Point2<f32>> {
    let ball_pos_start: Point2<f32> = [120.0, 120.0].into();
    let ball_pos_end: Point2<f32> = [120.0, 420.0].into();

    if let EasingEnum::EaseInOut3Point = ease_enum {
        let mid_pos = ease(Linear, ball_pos_start, ball_pos_end, 0.33);
        keyframes![
            (ball_pos_start, 0.0, EaseInOut),
            (mid_pos, 0.66 * duration, EaseInOut), // reach about a third of the height at two thirds of the duration
            (ball_pos_end, duration, EaseInOut)
        ]
    } else {
        keyframes![
            (ball_pos_start, 0.0, easing_function(ease_enum)),
            (ball_pos_end, duration, easing_function(ease_enum)) // this second function is necessary here because the sequence might get reversed
        ]
    }
}

#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq)]
#[repr(i32)]
enum AnimationType {
    Idle,
    Run,
    FrontFlip,
    Roll,
    Crawl,
}

const FRAME_ROWS: i32 = 19;
/// returns the src.y parameter for the animation
fn src_y(anim_type: &AnimationType) -> f32 {
    let row = match anim_type {
        AnimationType::Idle => 1,
        AnimationType::Run => 3,
        AnimationType::FrontFlip => 8,
        AnimationType::Roll => 11,
        AnimationType::Crawl => 10,
    };

    row as f32 / FRAME_ROWS as f32
}

const FRAME_COLUMNS: i32 = 14;
/// returns the final src.x parameter for the last frame of the animation
fn src_x_end(anim_type: &AnimationType) -> f32 {
    (frame_count(anim_type) - 1) as f32 / FRAME_COLUMNS as f32
}

fn frame_count(anim_type: &AnimationType) -> i32 {
    match anim_type {
        AnimationType::Idle => 7,
        AnimationType::Run => 8,
        AnimationType::FrontFlip => 14,
        AnimationType::Roll => 10,
        AnimationType::Crawl => 8,
    }
}

#[derive(CanTween, Clone, Copy)]
/// necessary because we can't implement CanTween for graphics::Rect directly, as it's a foreign type
struct TweenableRect {
    x: f32,
    y: f32,
    w: f32,
    h: f32,
}

impl TweenableRect {
    fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
        TweenableRect { x, y, w, h }
    }
}

impl From<TweenableRect> for Rect {
    fn from(t_rect: TweenableRect) -> Self {
        Rect {
            x: t_rect.x,
            y: t_rect.y,
            w: t_rect.w,
            h: t_rect.h,
        }
    }
}

/// A fancy easing function, tweening something into one of `frames` many discrete states.
/// The `pre_easing` is applied first, thereby making other `EasingFunction`s usable in the realm of frame-by-frame animation
struct AnimationFloor {
    pre_easing: Box<dyn EasingFunction + Send + Sync>,
    frames: i32,
}
impl EasingFunction for AnimationFloor {
    #[inline]
    fn y(&self, x: f64) -> f64 {
        (self.pre_easing.y(x) * (self.frames) as f64).floor() / (self.frames - 1) as f64
    }
}

fn player_sequence(
    ease_enum: &EasingEnum,
    anim_type: &AnimationType,
    duration: f32,
) -> AnimationSequence<TweenableRect> {
    // create the two Rects that will serve as `from` and `to` for the DrawParam::src of the animation
    // the start for all animations is at the leftmost frame, starting at 0.0
    let src_x_start: f32 = 0.0;
    // the final parameter depends upon how many frames there are in an animation
    let src_x_end = src_x_end(anim_type);
    // the src.y parameter depends on the row in which the animation is placed inside the sprite sheet
    let src_y = src_y(anim_type);
    // the height and width of the source rect are the proportions of a frame relative towards the whole sprite sheet
    let w = 1.0 / FRAME_COLUMNS as f32;
    let h = 1.0 / FRAME_ROWS as f32;
    let src_rect_start = TweenableRect::new(src_x_start, src_y, w, h);
    let src_end_rect = TweenableRect::new(src_x_end, src_y, w, h);

    let frames = frame_count(anim_type);

    if let EasingEnum::EaseInOut3Point = ease_enum {
        // first calculate the middle state of this sequence
        // luckily we can use keyframe to help us with that
        let mid = ease(
            AnimationFloor {
                pre_easing: Box::new(Linear),
                frames,
            },
            src_rect_start,
            src_end_rect,
            0.33,
        );
        let mid_frames = (frames as f32 * 0.33).floor() as i32;
        // we need to adapt the frame count for each keyframe
        // only the frames that are to be played until the next keyframe count
        keyframes![
            (
                src_rect_start,
                0.0,
                AnimationFloor {
                    pre_easing: Box::new(EaseInOut),
                    frames: mid_frames + 1
                }
            ),
            (
                mid,
                0.66 * duration,
                AnimationFloor {
                    pre_easing: Box::new(EaseInOut),
                    frames: frames - mid_frames
                }
            ),
            (src_end_rect, duration)
        ]
    } else {
        // the simpler case: choose some easing function as the pre-easing of an AnimationFloor
        // which operates on all frames, from the first to the last
        let easing = AnimationFloor {
            pre_easing: easing_function(ease_enum),
            frames,
        };
        keyframes![
            (src_rect_start, 0.0, easing),
            (src_end_rect, duration) // we don't need to specify a second easing function,
                                     // since this sequence won't be reversed, leading to
                                     // it never being used anyway
        ]
    }
}

impl MainState {
    fn new(ctx: &mut Context) -> GameResult<MainState> {
        let mut img = graphics::Image::new(ctx, "/player_sheet.png")?;
        img.set_filter(FilterMode::Nearest); // because pixel art
        let s = MainState {
            spritesheet: img,
            easing_enum: EasingEnum::Linear,
            animation_type: AnimationType::Idle,
            ball_animation: ball_sequence(&EasingEnum::Linear, 1.0),
            player_animation: player_sequence(&EasingEnum::Linear, &AnimationType::Idle, 1.0),
            duration: 1.0,
        };
        Ok(s)
    }
}

fn draw_info(ctx: &mut Context, info: String, position: Point2<f32>) -> GameResult {
    let t = Text::new(TextFragment {
        text: info,
        font: None,
        scale: Some(ggez::graphics::PxScale::from(40.0)),
        ..Default::default()
    });
    graphics::draw(
        ctx,
        &t,
        DrawParam::default().dest(position).color(Color::WHITE),
    )
}

impl event::EventHandler<ggez::GameError> for MainState {
    fn update(&mut self, ctx: &mut Context) -> GameResult {
        let secs = ggez::timer::delta(ctx).as_secs_f64();
        // advance the ball animation and reverse it once it reaches its end
        self.ball_animation.advance_and_maybe_reverse(secs);
        // advance the player animation and wrap around back to the beginning once it reaches its end
        self.player_animation.advance_and_maybe_wrap(secs);
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult {
        graphics::clear(ctx, [0.1, 0.2, 0.3, 1.0].into());

        // draw some text showing the current parameters
        draw_info(
            ctx,
            format!("Easing: {:?}", self.easing_enum),
            [300.0, 60.0].into(),
        )?;
        draw_info(
            ctx,
            format!("Animation: {:?}", self.animation_type),
            [300.0, 110.0].into(),
        )?;
        draw_info(
            ctx,
            format!("Duration: {:.2} s", self.duration),
            [300.0, 160.0].into(),
        )?;

        // draw the animated ball
        let ball = graphics::Mesh::new_circle(
            ctx,
            graphics::DrawMode::fill(),
            Vec2::new(0.0, 0.0),
            60.0,
            1.0,
            Color::WHITE,
        )?;
        let ball_pos = self.ball_animation.now_strict().unwrap();
        graphics::draw(ctx, &ball, (ball_pos,))?;

        // draw the player
        let current_frame_src: Rect = self.player_animation.now_strict().unwrap().into();
        let scale = 3.0;
        let draw_p = DrawParam::default()
            .src(current_frame_src)
            .scale(Vec2::new(scale, scale))
            .dest([470.0, 460.0])
            .offset([0.5, 1.0]);
        graphics::draw(ctx, &self.spritesheet, draw_p)?;

        graphics::present(ctx)?;
        Ok(())
    }

    fn key_down_event(
        &mut self,
        _ctx: &mut Context,
        keycode: ggez::input::keyboard::KeyCode,
        _keymods: ggez::input::keyboard::KeyMods,
        _repeat: bool,
    ) -> GameResult {
        const DELTA: f32 = 0.2;
        match keycode {
            KeyCode::Up | KeyCode::Down => {
                // easing change
                let new_easing_enum = new_enum_after_key(
                    &self.easing_enum,
                    &EasingEnum::EaseInOut3Point,
                    &KeyCode::Down,
                    &KeyCode::Up,
                    &keycode,
                );

                if self.easing_enum != new_easing_enum {
                    self.easing_enum = new_easing_enum;
                }
            }
            KeyCode::Left | KeyCode::Right => {
                // animation change
                let new_animation_type = new_enum_after_key(
                    &self.animation_type,
                    &AnimationType::Crawl,
                    &KeyCode::Left,
                    &KeyCode::Right,
                    &keycode,
                );

                if self.animation_type != new_animation_type {
                    self.animation_type = new_animation_type;
                }
            }
            // duration change
            KeyCode::W => {
                self.duration += DELTA;
            }
            KeyCode::S => {
                if self.duration - DELTA > 0.1 {
                    self.duration -= DELTA;
                }
            }
            _ => {}
        }

        self.ball_animation = ball_sequence(&self.easing_enum, self.duration);
        self.player_animation =
            player_sequence(&self.easing_enum, &self.animation_type, self.duration);
        Ok(())
    }
}

fn new_enum_after_key<E: ToPrimitive + FromPrimitive>(
    old_enum: &E,
    max_enum: &E,
    dec_key: &KeyCode,
    inc_key: &KeyCode,
    key: &KeyCode,
) -> E {
    let mut new_val = ToPrimitive::to_i32(old_enum).unwrap();
    new_val += match key {
        _ if *key == *dec_key => -1,
        _ if *key == *inc_key => 1,
        _ => 0,
    };

    let max_val = ToPrimitive::to_i32(max_enum).unwrap();
    if new_val < 0 {
        new_val = max_val;
    } else if new_val > max_val {
        new_val = 0;
    }

    FromPrimitive::from_i32(new_val).unwrap()
}

pub fn main() -> GameResult {
    let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
        let mut path = path::PathBuf::from(manifest_dir);
        path.push("resources");
        path
    } else {
        path::PathBuf::from("./resources")
    };

    let cb = ggez::ContextBuilder::new("animation example", "ggez").add_resource_path(resource_dir);
    let (mut ctx, event_loop) = cb.build()?;
    let state = MainState::new(&mut ctx)?;

    // instructions
    println!("CONTROLS:");
    println!("Left/Right: change animation");
    println!("Up/Down: change easing function");
    println!("W/S: change duration");

    event::run(ctx, event_loop, state)
}
<commit_msg>port animation example<commit_after>//! An example showcasing animation using `keyframe`.
//! Includes tweening and frame-by-frame animation.
//! Credit for the animation goes to [Dead Revolver](https://deadrevolver.itch.io/pixel-prototype-player-sprites)

#[macro_use]
extern crate num_derive;

use ggez::event;
use ggez::graphics::{self, Color};
use ggez::input::keyboard::KeyCode;
use ggez::mint::Point2;
use ggez::{Context, GameResult};
use glam::*;
use keyframe::{ease, functions::*, keyframes, AnimationSequence, EasingFunction};
use keyframe_derive::CanTween;
use num_traits::{FromPrimitive, ToPrimitive};
use std::env;
use std::path;

struct MainState {
    ball: graphics::Mesh,
    spritesheet: graphics::Image,
    easing_enum: EasingEnum,
    animation_type: AnimationType,
    ball_animation: AnimationSequence<Point2<f32>>,
    player_animation: AnimationSequence<TweenableRect>,
    duration: f32,
}

#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq)]
#[repr(i32)]
enum EasingEnum {
    Linear,
    EaseIn,
    EaseInOut,
    EaseOut,
    EaseInCubic,
    EaseOutCubic,
    EaseInOutCubic,
    Bezier,
    EaseInOut3Point,
}

fn easing_function(ease_enum: &EasingEnum) -> Box<dyn EasingFunction + Send + Sync> {
    match ease_enum {
        EasingEnum::Linear => Box::new(Linear),
        EasingEnum::EaseIn => Box::new(EaseIn),
        EasingEnum::EaseInOut => Box::new(EaseInOut),
        EasingEnum::EaseOut => Box::new(EaseOut),
        EasingEnum::EaseInCubic => Box::new(EaseInCubic),
        EasingEnum::EaseOutCubic => Box::new(EaseOutCubic),
        EasingEnum::EaseInOutCubic => Box::new(EaseInOutCubic),
        EasingEnum::Bezier => Box::new(BezierCurve::from([0.6, 0.04].into(), [0.98, 0.335].into())),
        _ => panic!(),
    }
}

fn ball_sequence(ease_enum: &EasingEnum, duration: f32) -> AnimationSequence<Point2<f32>> {
    let ball_pos_start: Point2<f32> = [120.0, 120.0].into();
    let ball_pos_end: Point2<f32> = [120.0, 420.0].into();

    if let EasingEnum::EaseInOut3Point = ease_enum {
        let mid_pos = ease(Linear, ball_pos_start, ball_pos_end, 0.33);
        keyframes![
            (ball_pos_start, 0.0, EaseInOut),
            (mid_pos, 0.66 * duration, EaseInOut), // reach about a third of the height at two thirds of the duration
            (ball_pos_end, duration, EaseInOut)
        ]
    } else {
        keyframes![
            (ball_pos_start, 0.0, easing_function(ease_enum)),
            (ball_pos_end, duration, easing_function(ease_enum)) // this second function is necessary here because the sequence might get reversed
        ]
    }
}

#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq)]
#[repr(i32)]
enum AnimationType {
    Idle,
    Run,
    FrontFlip,
    Roll,
    Crawl,
}

const FRAME_ROWS: i32 = 19;
/// returns the src.y parameter for the animation
fn src_y(anim_type: &AnimationType) -> f32 {
    let row = match anim_type {
        AnimationType::Idle => 1,
        AnimationType::Run => 3,
        AnimationType::FrontFlip => 8,
        AnimationType::Roll => 11,
        AnimationType::Crawl => 10,
    };

    row as f32 / FRAME_ROWS as f32
}

const FRAME_COLUMNS: i32 = 14;
/// returns the final src.x parameter for the last frame of the animation
fn src_x_end(anim_type: &AnimationType) -> f32 {
    (frame_count(anim_type) - 1) as f32 / FRAME_COLUMNS as f32
}

fn frame_count(anim_type: &AnimationType) -> i32 {
    match anim_type {
        AnimationType::Idle => 7,
        AnimationType::Run => 8,
        AnimationType::FrontFlip => 14,
        AnimationType::Roll => 10,
        AnimationType::Crawl => 8,
    }
}

#[derive(CanTween, Clone, Copy)]
/// necessary because we can't implement CanTween for graphics::Rect directly, as it's a foreign type
struct TweenableRect {
    x: f32,
    y: f32,
    w: f32,
    h: f32,
}

impl TweenableRect {
    fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
        TweenableRect { x, y, w, h }
    }
}

impl From<TweenableRect> for graphics::Rect {
    fn from(t_rect: TweenableRect) -> Self {
        graphics::Rect {
            x: t_rect.x,
            y: t_rect.y,
            w: t_rect.w,
            h: t_rect.h,
        }
    }
}

/// A fancy easing function, tweening something into one of `frames` many discrete states.
/// The `pre_easing` is applied first, thereby making other `EasingFunction`s usable in the realm of frame-by-frame animation
struct AnimationFloor {
    pre_easing: Box<dyn EasingFunction + Send + Sync>,
    frames: i32,
}
impl EasingFunction for AnimationFloor {
    #[inline]
    fn y(&self, x: f64) -> f64 {
        (self.pre_easing.y(x) * (self.frames) as f64).floor() / (self.frames - 1) as f64
    }
}

fn player_sequence(
    ease_enum: &EasingEnum,
    anim_type: &AnimationType,
    duration: f32,
) -> AnimationSequence<TweenableRect> {
    // create the two Rects that will serve as `from` and `to` for the DrawParam::src of the animation
    // the start for all animations is at the leftmost frame, starting at 0.0
    let src_x_start: f32 = 0.0;
    // the final parameter depends upon how many frames there are in an animation
    let src_x_end = src_x_end(anim_type);
    // the src.y parameter depends on the row in which the animation is placed inside the sprite sheet
    let src_y = src_y(anim_type);
    // the height and width of the source rect are the proportions of a frame relative towards the whole sprite sheet
    let w = 1.0 / FRAME_COLUMNS as f32;
    let h = 1.0 / FRAME_ROWS as f32;
    let src_rect_start = TweenableRect::new(src_x_start, src_y, w, h);
    let src_end_rect = TweenableRect::new(src_x_end, src_y, w, h);

    let frames = frame_count(anim_type);

    if let EasingEnum::EaseInOut3Point = ease_enum {
        // first calculate the middle state of this sequence
        // luckily we can use keyframe to help us with that
        let mid = ease(
            AnimationFloor {
                pre_easing: Box::new(Linear),
                frames,
            },
            src_rect_start,
            src_end_rect,
            0.33,
        );
        let mid_frames = (frames as f32 * 0.33).floor() as i32;
        // we need to adapt the frame count for each keyframe
        // only the frames that are to be played until the next keyframe count
        keyframes![
            (
                src_rect_start,
                0.0,
                AnimationFloor {
                    pre_easing: Box::new(EaseInOut),
                    frames: mid_frames + 1
                }
            ),
            (
                mid,
                0.66 * duration,
                AnimationFloor {
                    pre_easing: Box::new(EaseInOut),
                    frames: frames - mid_frames
                }
            ),
            (src_end_rect, duration)
        ]
    } else {
        // the simpler case: choose some easing function as the pre-easing of an AnimationFloor
        // which operates on all frames, from the first to the last
        let easing = AnimationFloor {
            pre_easing: easing_function(ease_enum),
            frames,
        };
        keyframes![
            (src_rect_start, 0.0, easing),
            (src_end_rect, duration) // we don't need to specify a second easing function,
                                     // since this sequence won't be reversed, leading to
                                     // it never being used anyway
        ]
    }
}

impl MainState {
    fn new(ctx: &mut Context) -> GameResult<MainState> {
        ctx.gfx.add_font(
            "LiberationMono",
            graphics::FontData::from_path(&ctx.filesystem, "/LiberationMono-Regular.ttf")?,
        );

        let ball = graphics::Mesh::new_circle(
            &ctx.gfx,
            graphics::DrawMode::fill(),
            Vec2::new(0.0, 0.0),
            60.0,
            1.0,
            Color::WHITE,
        )?;

        let img = graphics::Image::from_path(ctx, "/player_sheet.png", true)?;
        let s = MainState {
            ball,
            spritesheet: img,
            easing_enum: EasingEnum::Linear,
            animation_type: AnimationType::Idle,
            ball_animation: ball_sequence(&EasingEnum::Linear, 1.0),
            player_animation: player_sequence(&EasingEnum::Linear, &AnimationType::Idle, 1.0),
            duration: 1.0,
        };
        Ok(s)
    }
}

fn draw_info(canvas: &mut graphics::Canvas, info: String, position: Point2<f32>) -> GameResult {
    let t = graphics::Text::new()
        .text(info)
        .font("LiberationMono")
        .size(40.)
        .color(Color::WHITE);
    canvas.draw_text(&[t], position, graphics::TextLayout::tl_single_line())
}

impl event::EventHandler<ggez::GameError> for MainState {
    fn update(&mut self, ctx: &mut Context) -> GameResult {
        let secs = ctx.timer.delta().as_secs_f64();
        // advance the ball animation and reverse it once it reaches its end
        self.ball_animation.advance_and_maybe_reverse(secs);
        // advance the player animation and wrap around back to the beginning once it reaches its end
        self.player_animation.advance_and_maybe_wrap(secs);
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult {
        let mut canvas =
            graphics::Canvas::from_frame(&mut ctx.gfx, Color::from([0.1, 0.2, 0.3, 1.0]));

        canvas.set_sampler(graphics::Sampler::nearest_clamp()); // because pixel art

        // draw some text showing the current parameters
        draw_info(
            &mut canvas,
            format!("Easing: {:?}", self.easing_enum),
            [300.0, 60.0].into(),
        )?;
        draw_info(
            &mut canvas,
            format!("Animation: {:?}", self.animation_type),
            [300.0, 110.0].into(),
        )?;
        draw_info(
            &mut canvas,
            format!("Duration: {:.2} s", self.duration),
            [300.0, 160.0].into(),
        )?;

        // draw the animated ball
        let ball_pos = self.ball_animation.now_strict().unwrap();
        canvas.draw_mesh(
            &self.ball,
            None,
            graphics::DrawParam::new().offset(ball_pos),
        );

        // draw the player
        let current_frame_src: graphics::Rect = self.player_animation.now_strict().unwrap().into();
        let scale = 3.0;
        canvas.draw(
            &self.spritesheet,
            graphics::DrawParam::new()
                .src_rect(current_frame_src)
                .scale([scale, scale])
                .offset([470.0, 460.0])
                .origin([0.5, 1.0]),
        );

        canvas.finish();

        Ok(())
    }

    fn key_down_event(
        &mut self,
        _ctx: &mut Context,
        keycode: ggez::input::keyboard::KeyCode,
        _keymods: ggez::input::keyboard::KeyMods,
        _repeat: bool,
    ) -> GameResult {
        const DELTA: f32 = 0.2;
        match keycode {
            KeyCode::Up | KeyCode::Down => {
                // easing change
                let new_easing_enum = new_enum_after_key(
                    &self.easing_enum,
                    &EasingEnum::EaseInOut3Point,
                    &KeyCode::Down,
                    &KeyCode::Up,
                    &keycode,
                );

                if self.easing_enum != new_easing_enum {
                    self.easing_enum = new_easing_enum;
                }
            }
            KeyCode::Left | KeyCode::Right => {
                // animation change
                let new_animation_type = new_enum_after_key(
                    &self.animation_type,
                    &AnimationType::Crawl,
                    &KeyCode::Left,
                    &KeyCode::Right,
                    &keycode,
                );

                if self.animation_type != new_animation_type {
                    self.animation_type = new_animation_type;
                }
            }
            // duration change
            KeyCode::W => {
                self.duration += DELTA;
            }
            KeyCode::S => {
                if self.duration - DELTA > 0.1 {
                    self.duration -= DELTA;
                }
            }
            _ => {}
        }

        self.ball_animation = ball_sequence(&self.easing_enum, self.duration);
        self.player_animation =
            player_sequence(&self.easing_enum, &self.animation_type, self.duration);
        Ok(())
    }
}

fn new_enum_after_key<E: ToPrimitive + FromPrimitive>(
    old_enum: &E,
    max_enum: &E,
    dec_key: &KeyCode,
    inc_key: &KeyCode,
    key: &KeyCode,
) -> E {
    let mut new_val = ToPrimitive::to_i32(old_enum).unwrap();
    new_val += match key {
        _ if *key == *dec_key => -1,
        _ if *key == *inc_key => 1,
        _ => 0,
    };

    let max_val = ToPrimitive::to_i32(max_enum).unwrap();
    if new_val < 0 {
        new_val = max_val;
    } else if new_val > max_val {
        new_val = 0;
    }

    FromPrimitive::from_i32(new_val).unwrap()
}

pub fn main() -> GameResult {
    let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
        let mut path = path::PathBuf::from(manifest_dir);
        path.push("resources");
        path
    } else {
        path::PathBuf::from("./resources")
    };

    let cb = ggez::ContextBuilder::new("animation example", "ggez").add_resource_path(resource_dir);
    let (mut ctx, event_loop) = cb.build()?;
    let state = MainState::new(&mut ctx)?;

    // instructions
    println!("CONTROLS:");
    println!("Left/Right: change animation");
    println!("Up/Down: change easing function");
    println!("W/S: change duration");

    event::run(ctx, event_loop, state)
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;

#[path = "common/boilerplate.rs"]
mod boilerplate;

use boilerplate::{Example, HandyDandyRectBuilder};
use euclid::SideOffsets2D;
use webrender::api::*;
use winit::dpi::LogicalPosition;

struct App {
    cursor_position: WorldPoint,
}

impl Example for App {
    fn render(
        &mut self,
        _api: &RenderApi,
        builder: &mut DisplayListBuilder,
        _txn: &mut Transaction,
        _framebuffer_size: DeviceUintSize,
        _pipeline_id: PipelineId,
        _document_id: DocumentId,
    ) {
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::zero(), builder.content_size())
        );
        builder.push_stacking_context(
            &info,
            None,
            TransformStyle::Flat,
            MixBlendMode::Normal,
            Vec::new(),
            RasterSpace::Screen,
        );

        if true {
            // scrolling and clips stuff
            // let's make a scrollbox
            let scrollbox = (0, 0).to(300, 400);
            builder.push_stacking_context(
                &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
                None,
                TransformStyle::Flat,
                MixBlendMode::Normal,
                Vec::new(),
                RasterSpace::Screen,
            );
            // set the scrolling clip
            let clip_id = builder.define_scroll_frame(
                None,
                (0, 0).by(1000, 1000),
                scrollbox,
                vec![],
                None,
                ScrollSensitivity::ScriptAndInputEvents,
            );
            builder.push_clip_id(clip_id);

            // now put some content into it.
            // start with a white background
            let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
            info.tag = Some((0, 1));
            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));

            // let's make a 50x50 blue square as a visual reference
            let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
            info.tag = Some((0, 2));
            builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));

            // and a 50x50 green square next to it with an offset clip
            // to see what that looks like
            let mut info =
                LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
            info.tag = Some((0, 3));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));

            // Below the above rectangles, set up a nested scrollbox. It's still in
            // the same stacking context, so note that the rects passed in need to
            // be relative to the stacking context.
            let nested_clip_id = builder.define_scroll_frame(
                None,
                (0, 100).to(300, 1000),
                (0, 100).to(200, 300),
                vec![],
                None,
                ScrollSensitivity::ScriptAndInputEvents,
            );
            builder.push_clip_id(nested_clip_id);

            // give it a giant gray background just to distinguish it and to easily
            // visually identify the nested scrollbox
            let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
            info.tag = Some((0, 4));
            builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));

            // add a teal square to visualize the scrolling/clipping behaviour
            // as you scroll the nested scrollbox
            let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
            info.tag = Some((0, 5));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));

            // Add a sticky frame. It will "stick" twice while scrolling, once
            // at a margin of 10px from the bottom, for 40 pixels of scrolling,
            // and once at a margin of 10px from the top, for 60 pixels of
            // scrolling.
            let sticky_id = builder.define_sticky_frame(
                (50, 350).by(50, 50),
                SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                StickyOffsetBounds::new(-40.0, 60.0),
                StickyOffsetBounds::new(0.0, 0.0),
                LayoutVector2D::new(0.0, 0.0)
            );

            builder.push_clip_id(sticky_id);
            let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
            info.tag = Some((0, 6));
            builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
            builder.pop_clip_id(); // sticky_id

            // just for good measure add another teal square further down and to
            // the right, which can be scrolled into view by the user
            let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
            info.tag = Some((0, 7));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));

            builder.pop_clip_id(); // nested_clip_id

            builder.pop_clip_id(); // clip_id
            builder.pop_stacking_context();
        }

        builder.pop_stacking_context();
    }

    fn on_event(&mut self, event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
        let mut txn = Transaction::new();
        match event {
            winit::WindowEvent::KeyboardInput {
                input: winit::KeyboardInput {
                    state: winit::ElementState::Pressed,
                    virtual_keycode: Some(key),
                    ..
                },
                ..
            } => {
                let offset = match key {
                    winit::VirtualKeyCode::Down => (0.0, -10.0),
                    winit::VirtualKeyCode::Up => (0.0, 10.0),
                    winit::VirtualKeyCode::Right => (-10.0, 0.0),
                    winit::VirtualKeyCode::Left => (10.0, 0.0),
                    _ => return false,
                };

                txn.scroll(
                    ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                    self.cursor_position,
                );
            }
            winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
                self.cursor_position = WorldPoint::new(x as f32, y as f32);
            }
            winit::WindowEvent::MouseWheel { delta, .. } => {
                const LINE_HEIGHT: f32 = 38.0;
                let (dx, dy) = match delta {
                    winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                    winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
                };

                txn.scroll(
                    ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                    self.cursor_position,
                );
            }
            winit::WindowEvent::MouseInput { .. } => {
                let results = api.hit_test(
                    document_id,
                    None,
                    self.cursor_position,
                    HitTestFlags::FIND_ALL
                );

                println!("Hit test results:");
                for item in &results.items {
                    println!("  • {:?}", item);
                }
                println!("");
            }
            _ => (),
        }

        api.send_transaction(document_id, txn);

        false
    }
}

fn main() {
    let mut app = App {
        cursor_position: WorldPoint::zero(),
    };
    boilerplate::main_wrapper(&mut app, None);
}
<commit_msg>Fix scrolling example to re-render on scroll<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;

#[path = "common/boilerplate.rs"]
mod boilerplate;

use boilerplate::{Example, HandyDandyRectBuilder};
use euclid::SideOffsets2D;
use webrender::api::*;
use winit::dpi::LogicalPosition;

struct App {
    cursor_position: WorldPoint,
}

impl Example for App {
    fn render(
        &mut self,
        _api: &RenderApi,
        builder: &mut DisplayListBuilder,
        _txn: &mut Transaction,
        _framebuffer_size: DeviceUintSize,
        _pipeline_id: PipelineId,
        _document_id: DocumentId,
    ) {
        let info = LayoutPrimitiveInfo::new(
            LayoutRect::new(LayoutPoint::zero(), builder.content_size())
        );
        builder.push_stacking_context(
            &info,
            None,
            TransformStyle::Flat,
            MixBlendMode::Normal,
            Vec::new(),
            RasterSpace::Screen,
        );

        if true {
            // scrolling and clips stuff
            // let's make a scrollbox
            let scrollbox = (0, 0).to(300, 400);
            builder.push_stacking_context(
                &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
                None,
                TransformStyle::Flat,
                MixBlendMode::Normal,
                Vec::new(),
                RasterSpace::Screen,
            );
            // set the scrolling clip
            let clip_id = builder.define_scroll_frame(
                None,
                (0, 0).by(1000, 1000),
                scrollbox,
                vec![],
                None,
                ScrollSensitivity::ScriptAndInputEvents,
            );
            builder.push_clip_id(clip_id);

            // now put some content into it.
            // start with a white background
            let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
            info.tag = Some((0, 1));
            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));

            // let's make a 50x50 blue square as a visual reference
            let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
            info.tag = Some((0, 2));
            builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));

            // and a 50x50 green square next to it with an offset clip
            // to see what that looks like
            let mut info =
                LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
            info.tag = Some((0, 3));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));

            // Below the above rectangles, set up a nested scrollbox. It's still in
            // the same stacking context, so note that the rects passed in need to
            // be relative to the stacking context.
            let nested_clip_id = builder.define_scroll_frame(
                None,
                (0, 100).to(300, 1000),
                (0, 100).to(200, 300),
                vec![],
                None,
                ScrollSensitivity::ScriptAndInputEvents,
            );
            builder.push_clip_id(nested_clip_id);

            // give it a giant gray background just to distinguish it and to easily
            // visually identify the nested scrollbox
            let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
            info.tag = Some((0, 4));
            builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));

            // add a teal square to visualize the scrolling/clipping behaviour
            // as you scroll the nested scrollbox
            let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
            info.tag = Some((0, 5));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));

            // Add a sticky frame. It will "stick" twice while scrolling, once
            // at a margin of 10px from the bottom, for 40 pixels of scrolling,
            // and once at a margin of 10px from the top, for 60 pixels of
            // scrolling.
            let sticky_id = builder.define_sticky_frame(
                (50, 350).by(50, 50),
                SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                StickyOffsetBounds::new(-40.0, 60.0),
                StickyOffsetBounds::new(0.0, 0.0),
                LayoutVector2D::new(0.0, 0.0)
            );

            builder.push_clip_id(sticky_id);
            let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
            info.tag = Some((0, 6));
            builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
            builder.pop_clip_id(); // sticky_id

            // just for good measure add another teal square further down and to
            // the right, which can be scrolled into view by the user
            let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
            info.tag = Some((0, 7));
            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));

            builder.pop_clip_id(); // nested_clip_id

            builder.pop_clip_id(); // clip_id
            builder.pop_stacking_context();
        }

        builder.pop_stacking_context();
    }

    fn on_event(&mut self, event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
        let mut txn = Transaction::new();
        match event {
            winit::WindowEvent::KeyboardInput {
                input: winit::KeyboardInput {
                    state: winit::ElementState::Pressed,
                    virtual_keycode: Some(key),
                    ..
                },
                ..
            } => {
                let offset = match key {
                    winit::VirtualKeyCode::Down => (0.0, -10.0),
                    winit::VirtualKeyCode::Up => (0.0, 10.0),
                    winit::VirtualKeyCode::Right => (-10.0, 0.0),
                    winit::VirtualKeyCode::Left => (10.0, 0.0),
                    _ => return false,
                };

                txn.scroll(
                    ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                    self.cursor_position,
                );
                txn.generate_frame();
            }
            winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
                self.cursor_position = WorldPoint::new(x as f32, y as f32);
            }
            winit::WindowEvent::MouseWheel { delta, .. } => {
                const LINE_HEIGHT: f32 = 38.0;
                let (dx, dy) = match delta {
                    winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                    winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
                };

                txn.scroll(
                    ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                    self.cursor_position,
                );
                txn.generate_frame();
            }
            winit::WindowEvent::MouseInput { .. } => {
                let results = api.hit_test(
                    document_id,
                    None,
                    self.cursor_position,
                    HitTestFlags::FIND_ALL
                );

                println!("Hit test results:");
                for item in &results.items {
                    println!("  • {:?}", item);
                }
                println!("");
            }
            _ => (),
        }

        api.send_transaction(document_id, txn);

        false
    }
}

fn main() {
    let mut app = App {
        cursor_position: WorldPoint::zero(),
    };
    boilerplate::main_wrapper(&mut app, None);
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
extern crate polydraw;

use std::iter;

use polydraw::{Application, Renderer, Frame};
use polydraw::geom::point::Point;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum EdgeType {
   Inclined,
   InclinedRev,
   Horizontal,
   HorizontalRev,
   Vertical,
   VerticalRev,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Edge {
   etype: EdgeType,
   p1: usize,
   p2: usize,
}

impl Edge {
   #[inline]
   pub fn new(etype: EdgeType, p1: usize, p2: usize) -> Self {
      Edge {
         etype: etype,
         p1: p1,
         p2: p2,
      }
   }
}

impl Default for Edge {
   #[inline]
   fn default() -> Edge {
      Edge::new(EdgeType::Inclined, 0, 0)
   }
}

pub fn fill_default<T>(capacity: usize) -> Vec<T> where T: Default + Clone {
   iter::repeat(T::default()).take(capacity).collect()
}

struct TriangleRenderer {
   triangles: Vec<Edge>,
   original: Vec<Point>,

   above: Vec<Edge>,
   below: Vec<Edge>,
   points: Vec<Point>,
}

impl TriangleRenderer {
   fn new() -> Self {
      let triangles = vec![
         // A
         Edge::new(EdgeType::Vertical, 0, 1),
         Edge::new(EdgeType::InclinedRev, 1, 2),
         Edge::new(EdgeType::InclinedRev, 2, 0),

         // B
         Edge::new(EdgeType::Inclined, 0, 2),
         Edge::new(EdgeType::InclinedRev, 2, 3),
         Edge::new(EdgeType::InclinedRev, 3, 0),

         // C
         Edge::new(EdgeType::Inclined, 0, 3),
         Edge::new(EdgeType::InclinedRev, 3, 4),
         Edge::new(EdgeType::HorizontalRev, 4, 0),

         // D
         Edge::new(EdgeType::Inclined, 4, 3),
         Edge::new(EdgeType::Inclined, 3, 5),
         Edge::new(EdgeType::InclinedRev, 5, 4),

         // E
         Edge::new(EdgeType::Inclined, 4, 5),
         Edge::new(EdgeType::Inclined, 5, 6),
         Edge::new(EdgeType::VerticalRev, 6, 4),

         // F
         Edge::new(EdgeType::Inclined, 3, 2),
         Edge::new(EdgeType::Inclined, 2, 7),
         Edge::new(EdgeType::InclinedRev, 7, 3),

         // G
         Edge::new(EdgeType::Inclined, 3, 7),
         Edge::new(EdgeType::InclinedRev, 7, 5),
         Edge::new(EdgeType::InclinedRev, 5, 3),

         // H
         Edge::new(EdgeType::Inclined, 2, 1),
         Edge::new(EdgeType::InclinedRev, 1, 7),
         Edge::new(EdgeType::InclinedRev, 7, 2),

         // I
         Edge::new(EdgeType::Inclined, 5, 7),
         Edge::new(EdgeType::Inclined, 7, 6),
         Edge::new(EdgeType::InclinedRev, 6, 5),

         // J
         Edge::new(EdgeType::Inclined, 7, 1),
         Edge::new(EdgeType::Horizontal, 1, 6),
         Edge::new(EdgeType::InclinedRev, 6, 7),
      ];

      let original = vec![
         Point::new(0, 0),   // 0
         Point::new(0, 10),  // 1
         Point::new(2, 5),   // 2
         Point::new(3, 1),   // 3
         Point::new(10, 0),  // 4
         Point::new(8, 5),   // 5
         Point::new(10, 10), // 6
         Point::new(7, 9),   // 7
      ];

      TriangleRenderer {
         triangles: triangles,
         original: original,

         above: fill_default(262144),
         below: fill_default(262144),
         points: fill_default(262144),
      }
   }
}

impl Renderer for TriangleRenderer {
   fn render(&mut self, frame: &mut Frame) {
      frame.clear();
   }
}

fn main() {
   let mut renderer = TriangleRenderer::new();

   Application::new()
      .renderer(&mut renderer)
      .title("Triangle3")
      .run();
}
<commit_msg>Improving the triangle data source model<commit_after>#![allow(dead_code)]
extern crate polydraw;

use polydraw::{Application, Renderer, Frame};
use polydraw::geom::point::Point;
use polydraw::geom::ring::Ring;
use polydraw::draw::RGB;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct Poly {
   color: RGB,
   start: usize,
   end: usize,
}

impl Poly {
   #[inline]
   pub fn new(color: RGB, start: usize, end: usize) -> Self {
      Poly {
         color: color,
         start: start,
         end: end,
      }
   }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Edge {
   Inclined(usize),
   InclinedRev(usize),
   Horizontal(i64),
   HorizontalRev(i64),
   Vertical(i64),
   VerticalRev(i64),
}

impl Default for Edge {
   #[inline]
   fn default() -> Edge {
      Edge::Inclined(0)
   }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct InclinedEdge {
   p1: usize,
   p2: usize,
}

impl InclinedEdge {
   #[inline]
   pub fn new(p1: usize, p2: usize) -> Self {
      InclinedEdge {
         p1: p1,
         p2: p2,
      }
   }
}

impl Default for InclinedEdge {
   #[inline]
   fn default() -> InclinedEdge {
      InclinedEdge::new(0, 0)
   }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct PolyRef {
   src: usize,
   start: usize,
   end: usize,
}

impl PolyRef {
   #[inline]
   pub fn new(src: usize, start: usize, end: usize) -> Self {
      PolyRef {
         src: src,
         start: start,
         end: end,
      }
   }
}

impl Default for PolyRef {
   #[inline]
   fn default() -> PolyRef {
      PolyRef::new(0, 0, 0)
   }
}

struct PolySource {
   polys: Vec<Poly>,
   edges: Vec<Edge>,
   inclined: Vec<InclinedEdge>,
   points: Vec<Point>,
}

impl PolySource {
   fn new() -> Self {
      let polys = vec![
         // A
         Poly::new(RGB::new(18, 78, 230), 0, 3),
         // B
         Poly::new(RGB::new(47, 11, 206), 3, 6),
         // C
         Poly::new(RGB::new(170, 44, 206), 6, 9),
         // D
         Poly::new(RGB::new(243, 0, 149), 9, 12),
         // E
         Poly::new(RGB::new(170, 36, 14), 12, 15),
         // F
         Poly::new(RGB::new(219, 65, 18), 15, 18),
         // G
         Poly::new(RGB::new(254, 185, 21), 18, 21),
         // H
         Poly::new(RGB::new(244, 239, 114), 21, 24),
         // I
         Poly::new(RGB::new(109, 233, 158), 24, 27),
         // J
         Poly::new(RGB::new(66, 222, 241), 27, 30),
      ];

      let edges = vec![
         // 0: A
         Edge::Vertical(0),
         Edge::InclinedRev(0),
         Edge::InclinedRev(1),

         // 1: B
         Edge::Inclined(1),
         Edge::InclinedRev(2),
         Edge::InclinedRev(3),

         // 2: C
         Edge::Inclined(3),
         Edge::InclinedRev(4),
         Edge::HorizontalRev(0),

         // 3: D
         Edge::Inclined(4),
         Edge::Inclined(5),
         Edge::InclinedRev(6),

         // 4: E
         Edge::Inclined(6),
         Edge::Inclined(7),
         Edge::VerticalRev(10),

         // 5: F
         Edge::Inclined(2),
         Edge::Inclined(8),
         Edge::InclinedRev(9),

         // 6: G
         Edge::Inclined(9),
         Edge::InclinedRev(10),
         Edge::InclinedRev(5),

         // 7: H
         Edge::Inclined(0),
         Edge::InclinedRev(11),
         Edge::InclinedRev(8),

         // 8: I
         Edge::Inclined(10),
         Edge::Inclined(12),
         Edge::InclinedRev(7),

         // 9: J
         Edge::Inclined(11),
         Edge::Horizontal(10),
         Edge::InclinedRev(12),
      ];

      let inclined = vec![
         InclinedEdge::new(2, 1),  // 0
         InclinedEdge::new(0, 2),  // 1
         InclinedEdge::new(3, 2),  // 2
         InclinedEdge::new(0, 3),  // 3
         InclinedEdge::new(4, 3),  // 4
         InclinedEdge::new(3, 5),  // 5
         InclinedEdge::new(4, 5),  // 6
         InclinedEdge::new(5, 6),  // 7
         InclinedEdge::new(2, 7),  // 8
         InclinedEdge::new(3, 7),  // 9
         InclinedEdge::new(5, 7),  // 10
         InclinedEdge::new(7, 1),  // 11
         InclinedEdge::new(7, 6),  // 12
      ];

      let points = vec![
         Point::new(0, 0),   // 0
         Point::new(0, 10),  // 1
         Point::new(2, 5),   // 2
         Point::new(3, 1),   // 3
         Point::new(10, 0),  // 4
         Point::new(8, 5),   // 5
         Point::new(10, 10), // 6
         Point::new(7, 9),   // 7
      ];

      PolySource {
         polys: polys,
         edges: edges,
         inclined: inclined,
         points: points,
      }
   }
}

struct TriangleRenderer {
   src: PolySource,

   upper: Ring<Edge>,
   upper_ref: Ring<PolyRef>,

   lower: Ring<Edge>,
   lower_ref: Ring<PolyRef>,

   points: Ring<Point>,
}

impl TriangleRenderer {
   fn new() -> Self {
      TriangleRenderer {
         src: PolySource::new(),

         upper: Ring::new(262144),
         upper_ref: Ring::new(65536),

         lower: Ring::new(262144),
         lower_ref: Ring::new(65536),

         points: Ring::new(262144),
      }
   }
}

impl Renderer for TriangleRenderer {
   fn render(&mut self, frame: &mut Frame) {
      frame.clear();
   }
}

fn main() {
   let mut renderer = TriangleRenderer::new();

   Application::new()
      .renderer(&mut renderer)
      .title("Triangle3")
      .run();
}
<|endoftext|>


<commit_before>use std::io::{self, Write};
use rand::{thread_rng, Rng};
use amqp::protocol::basic::{Deliver, BasicProperties};
use amqp::{self, Table, TableEntry};
use clap::ArgMatches;
use std::fs;
use std::path::Path;
use mime;
use client;
use output;
use error::RbtError;

pub fn do_subscribe(opts:amqp::Options, matches:&ArgMatches) -> Result<(),RbtError> {

    let output = try!(value_t!(matches, "output", String));
    let info = matches.is_present("info");

    // type lookup map
    let types = try!(mime::Types::new().or(Err("Failed to read mime types")));

    // check output is a dir
    {
        if output != "-" {
            let meta = try!(fs::metadata(&output));
            if !meta.is_dir() {
                rbterr!("Output {} is not a directory", output);
            }
        }
    }

    let receive = move |deliver:Deliver, props:BasicProperties, body:Vec<u8>| -> Result<(),RbtError> {

        let msg = try!(output::build_output(info, &deliver, &props, body));

        match output.as_ref() {
            "-" => {

                // just write to stdout
                let stdout = io::stdout();

                // lock until end of scope
                let mut handle = stdout.lock();

                try!(handle.write(&msg));
                try!(handle.write(b"\n"));
                try!(handle.flush());

            },
            _   => {

                // extract file name from headers, or fall back on random
                let file_name = file_name_of(&props, &types);

                // path relative to output dir
                let outdir = Path::new(&output);
                let path = Path::new(&output).join(file_name);

                // don't allow writes outside this dir
                if outdir != path.parent().unwrap() {
                    rbterr!("Output {} is not a directory", output);
                }

                let mut f = try!(fs::File::create(path));
                try!(f.write_all(&msg));

            },
        }

        Ok(())

    };

    let receiver = client::Receiver {
        exchange: try!(value_t!(matches, "exchange", String)),
        routing_key: try!(value_t!(matches, "routing_key", String)),
        callback: Box::new(receive),
    };

    client::open_receive(opts, receiver)
}

fn file_name_of(props:&BasicProperties, types:&mime::Types) -> String {

    let content_type =
        props.content_type.clone().unwrap_or("application/octet-stream".to_owned());

    // figure out a good extension for this content type
    let ext = (|| {
        // we can get more than one filename extension for
        // a type, e.g. .jpg, .jpeg
        if let Some(x) = types.get_extension(&content_type) {
            if x.len() > 0 {
                return x[0].clone() // pick the first one
            }
        }
        "bin".to_owned()
    })();

    // prefer a fileName from headers, but fall back on
    // a random name.
    let headers = props.headers.clone().unwrap_or(Table::new());
    if let Some(entry) = headers.get("fileName") {
        if let TableEntry::LongString(ref f) = *entry {
            return f.clone();
        }
    }
    gen_rand_name(ext)
}

fn gen_rand_name(ext:String) -> String {
    // generate 16 ascii chars
    let mut rand:String = thread_rng().gen_ascii_chars().take(16).collect();
    rand.push_str(".");
    rand.push_str(&ext);
    rand
}
<commit_msg>errln output names<commit_after>use std::io::{self, Write};
use rand::{thread_rng, Rng};
use amqp::protocol::basic::{Deliver, BasicProperties};
use amqp::{self, Table, TableEntry};
use clap::ArgMatches;
use std::fs;
use std::path::Path;
use mime;
use client;
use output;
use error::RbtError;

pub fn do_subscribe(opts:amqp::Options, matches:&ArgMatches) -> Result<(),RbtError> {

    let output = try!(value_t!(matches, "output", String));
    let info = matches.is_present("info");

    // type lookup map
    let types = try!(mime::Types::new().or(Err("Failed to read mime types")));

    // check output is a dir
    {
        if output != "-" {
            let meta = try!(fs::metadata(&output));
            if !meta.is_dir() {
                rbterr!("Output {} is not a directory", output);
            }
        }
    }

    let receive = move |deliver:Deliver, props:BasicProperties, body:Vec<u8>| -> Result<(),RbtError> {

        let msg = try!(output::build_output(info, &deliver, &props, body));

        match output.as_ref() {
            "-" => {

                // just write to stdout
                let stdout = io::stdout();

                // lock until end of scope
                let mut handle = stdout.lock();

                try!(handle.write(&msg));
                try!(handle.write(b"\n"));
                try!(handle.flush());

            },
            _   => {

                // extract file name from headers, or fall back on random
                let file_name = file_name_of(&props, &types);

                // path relative to output dir
                let outdir = Path::new(&output);
                let path = Path::new(&output).join(file_name);

                // don't allow writes outside this dir
                if outdir != path.parent().unwrap() {
                    rbterr!("Output {} is not a directory", output);
                }

                errln!("{}", path.to_str().unwrap());

                let mut f = try!(fs::File::create(path));
                try!(f.write_all(&msg));

            },
        }

        Ok(())

    };

    let receiver = client::Receiver {
        exchange: try!(value_t!(matches, "exchange", String)),
        routing_key: try!(value_t!(matches, "routing_key", String)),
        callback: Box::new(receive),
    };

    client::open_receive(opts, receiver)
}

fn file_name_of(props:&BasicProperties, types:&mime::Types) -> String {

    let content_type =
        props.content_type.clone().unwrap_or("application/octet-stream".to_owned());

    // figure out a good extension for this content type
    let ext = (|| {
        // we can get more than one filename extension for
        // a type, e.g. .jpg, .jpeg
        if let Some(x) = types.get_extension(&content_type) {
            if x.len() > 0 {
                return x[0].clone() // pick the first one
            }
        }
        "bin".to_owned()
    })();

    // prefer a fileName from headers, but fall back on
    // a random name.
    let headers = props.headers.clone().unwrap_or(Table::new());
    if let Some(entry) = headers.get("fileName") {
        if let TableEntry::LongString(ref f) = *entry {
            return f.clone();
        }
    }
    gen_rand_name(ext)
}

fn gen_rand_name(ext:String) -> String {
    // generate 16 ascii chars
    let mut rand:String = thread_rng().gen_ascii_chars().take(16).collect();
    rand.push_str(".");
    rand.push_str(&ext);
    rand
}
<|endoftext|>