mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 04:18:10 +00:00
feat(core): add pareen & easer crates
[no changelog]
This commit is contained in:
parent
b9a55cf2a7
commit
212391fc37
30
core/embed/rust/Cargo.lock
generated
30
core/embed/rust/Cargo.lock
generated
@ -94,6 +94,15 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "easer"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fba524f8b83c9c5bde02c2bb1627de9d1f81980489a6d54168cdfd08c258f917"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -154,6 +163,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@ -189,11 +204,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pareen"
|
||||||
|
version = "0.3.3"
|
||||||
|
dependencies = [
|
||||||
|
"easer",
|
||||||
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -318,10 +342,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"cstr_core",
|
"cstr_core",
|
||||||
"cty",
|
"cty",
|
||||||
|
"easer",
|
||||||
"glob",
|
"glob",
|
||||||
"heapless",
|
"heapless",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"pareen",
|
||||||
"qrcodegen",
|
"qrcodegen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"spin",
|
"spin",
|
||||||
|
@ -105,8 +105,9 @@ features = ["ufmt"]
|
|||||||
default_features = false
|
default_features = false
|
||||||
|
|
||||||
[dependencies.num-traits]
|
[dependencies.num-traits]
|
||||||
version = "0.2.15"
|
version = "0.2.18"
|
||||||
default_features = false
|
default_features = false
|
||||||
|
features = ["libm"]
|
||||||
|
|
||||||
[dependencies.num-derive]
|
[dependencies.num-derive]
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -125,6 +126,18 @@ version = "0.2.2"
|
|||||||
[dependencies.unsize]
|
[dependencies.unsize]
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
||||||
|
[dependencies.pareen]
|
||||||
|
version = "0.3.3"
|
||||||
|
path = "../../../rust/pareen"
|
||||||
|
default-features = false
|
||||||
|
features = ["libm", "easer"]
|
||||||
|
|
||||||
|
[dependencies.easer]
|
||||||
|
version = "0.3.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["libm"]
|
||||||
|
|
||||||
|
|
||||||
# Build dependencies
|
# Build dependencies
|
||||||
|
|
||||||
[build-dependencies.bindgen]
|
[build-dependencies.bindgen]
|
||||||
|
13
rust/pareen/.gitignore
vendored
Normal file
13
rust/pareen/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# Vim
|
||||||
|
.*.swp
|
68
rust/pareen/CHANGELOG.md
Normal file
68
rust/pareen/CHANGELOG.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Version 0.3.3 (2023-08-01)
|
||||||
|
|
||||||
|
- Add support for `no_std`
|
||||||
|
|
||||||
|
## Version 0.3.2 (2023-06-03)
|
||||||
|
|
||||||
|
- Bump version
|
||||||
|
|
||||||
|
## Version 0.3.1 (2022-08-31)
|
||||||
|
|
||||||
|
- Fix docs
|
||||||
|
|
||||||
|
## Version 0.3.0 (2022-08-31)
|
||||||
|
|
||||||
|
- Make operator overloading of `Mul`, `Add` and `Sub` more flexible.
|
||||||
|
This may break compilation in some cases, since types are more generic now.
|
||||||
|
- Add `AnimWithDur` for easier composition of animations that have a fixed duration
|
||||||
|
- Internal refactoring: split into multiple modules
|
||||||
|
- Implement `Anim::{fst,snd,copied}`
|
||||||
|
- Implement `AnimWithDur::{sum,mean}` and simple linear regression
|
||||||
|
- Implement `Anim::{into_fn,into_box_fn}`
|
||||||
|
|
||||||
|
## Version 0.2.6 (2020-08-17)
|
||||||
|
|
||||||
|
- Make exponential slowdown of compile times less likely
|
||||||
|
- Add `cycle`
|
||||||
|
- Add `quadratic`
|
||||||
|
|
||||||
|
## Version 0.2.5 (2020-07-18)
|
||||||
|
|
||||||
|
- Add `Anim::repeat`, `Anim::hold` and `Anim::seq_continue`
|
||||||
|
- Allow boxed animations
|
||||||
|
- Expose `easer` library
|
||||||
|
|
||||||
|
## Version 0.2.4 (2020-07-16)
|
||||||
|
|
||||||
|
Yanked.
|
||||||
|
|
||||||
|
## Version 0.2.3 (2020-04-28)
|
||||||
|
|
||||||
|
- Fix compilation issue on rustc 1.43.0 (<https://github.com/leod/pareen/pull/7>)
|
||||||
|
|
||||||
|
## Version 0.2.1 (2020-01-18)
|
||||||
|
|
||||||
|
- Implement `Anim::as_ref`
|
||||||
|
|
||||||
|
## Version 0.2.0 (2020-01-13)
|
||||||
|
|
||||||
|
- `squeeze` no longer switches to a default value outside of the given range.
|
||||||
|
Use `squeeze_and_surround` as a replacement.
|
||||||
|
|
||||||
|
## Version 0.1.3 (2019-12-03)
|
||||||
|
|
||||||
|
- Render README.md on crates.io
|
||||||
|
|
||||||
|
## Version 0.1.2 (2019-12-02)
|
||||||
|
|
||||||
|
- No change
|
||||||
|
|
||||||
|
## Version 0.1.1 (2019-12-02)
|
||||||
|
|
||||||
|
- Improve documentation
|
||||||
|
|
||||||
|
## Version 0.1.0 (2019-12-02)
|
||||||
|
|
||||||
|
- Initial version
|
34
rust/pareen/Cargo.toml
Normal file
34
rust/pareen/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[package]
|
||||||
|
name = "pareen"
|
||||||
|
version = "0.3.3"
|
||||||
|
authors = ["leod <subtle.frustration@proton.me>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
description = "A small library for parameterized inbetweening"
|
||||||
|
homepage = "https://github.com/leod/pareen"
|
||||||
|
documentation = "https://docs.rs/pareen"
|
||||||
|
repository = "https://github.com/leod/pareen"
|
||||||
|
keywords = ["animation", "combinators", "tween", "easing"]
|
||||||
|
readme = "README.md"
|
||||||
|
exclude = [".github"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = ["alloc", "num-traits/std"]
|
||||||
|
alloc = []
|
||||||
|
libm = ["num-traits/libm"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num-traits = { version = "0.2", default-features = false }
|
||||||
|
easer = { version = "0.3.0", default-features = false, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_approx_eq = "1.0"
|
||||||
|
gnuplot = "0.0.32"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["std", "alloc", "libm", "easer"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "plots"
|
||||||
|
required-features = ["std"]
|
21
rust/pareen/LICENSE
Normal file
21
rust/pareen/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 leod
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
81
rust/pareen/README.md
Normal file
81
rust/pareen/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Pareen
|
||||||
|
[![Docs Status](https://docs.rs/pareen/badge.svg)](https://docs.rs/pareen)
|
||||||
|
[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/leod/pareen/blob/master/LICENSE)
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/pareen.svg)](https://crates.io/crates/pareen)
|
||||||
|
|
||||||
|
Pareen is a small Rust library for *par*ameterized inbetw*een*ing.
|
||||||
|
The intended application is in game programming, where you sometimes have
|
||||||
|
two discrete game states between which you want to transition smoothly for
|
||||||
|
visualization purposes.
|
||||||
|
|
||||||
|
Pareen gives you tools for composing animations that are parameterized by
|
||||||
|
time (i.e. mappings from time to some animated value) without constantly
|
||||||
|
having to pass around time variables; it hides the plumbing, so that you
|
||||||
|
need to provide time only once: when evaluating the animation.
|
||||||
|
|
||||||
|
Animations are composed similarly to Rust's iterators, so no memory
|
||||||
|
allocations are necessary. The downside to this is that it is diffcult to store
|
||||||
|
pareen's animations. The recommended approach is to construct and evaluate
|
||||||
|
animations on the fly.
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
I consider `pareen` to be an experimental approach, and I'm not sure if I'm
|
||||||
|
still happy with it. Anyway, the integration of easing functions could use
|
||||||
|
some love. Contributions are very much welcome!
|
||||||
|
|
||||||
|
Unfortunately, it looks like heavily nested animations can cause an exponential
|
||||||
|
slowdown in compilation time. For now, boxing intermediate animations serves as
|
||||||
|
a workaround, but this comes with a decrease of both readability and efficiency.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
```rust
|
||||||
|
// An animation returning a constant value
|
||||||
|
let anim1 = pareen::constant(1.0f64);
|
||||||
|
|
||||||
|
// Animations can be evaluated at any time
|
||||||
|
let value = anim1.eval(0.5);
|
||||||
|
|
||||||
|
// Animations can be played in sequence
|
||||||
|
let anim2 = anim1.seq(0.7, pareen::prop(0.25) + 0.5);
|
||||||
|
|
||||||
|
// Animations can be composed and transformed in various ways
|
||||||
|
let anim3 = anim2
|
||||||
|
.lerp(pareen::circle().cos())
|
||||||
|
.scale_min_max(5.0, 10.0)
|
||||||
|
.backwards(1.0)
|
||||||
|
.squeeze(3.0, 0.5..=1.0);
|
||||||
|
|
||||||
|
let anim4 = pareen::cubic(&[1.0, 2.0, 3.0, 4.0]) - anim3;
|
||||||
|
|
||||||
|
let value = anim4.eval(1.0);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Easer
|
||||||
|
Optionally, you can enable support for integrating easing functions from
|
||||||
|
[`easer`](https://docs.rs/easer/0.2.1/easer/index.html).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let first_anim = pareen::constant(2.0);
|
||||||
|
let second_anim = pareen::prop(1.0f32);
|
||||||
|
|
||||||
|
// Transition from first_anim to second_anim at time 0.5, applying cubic easing
|
||||||
|
// for 0.3 time units.
|
||||||
|
let anim = first_anim.seq_ease_in_out(
|
||||||
|
0.5,
|
||||||
|
easer::functions::Cubic,
|
||||||
|
0.3,
|
||||||
|
second_anim,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plots
|
||||||
|
There is an example that shows some animations as plots via
|
||||||
|
[RustGnuplot](https://github.com/SiegeLord/RustGnuplot) in
|
||||||
|
[examples/plots.rs](examples/plots.rs). Given that `gnuplot` has been
|
||||||
|
installed, it can be executed like this:
|
||||||
|
```bash
|
||||||
|
cargo run --example plots --feature easer
|
||||||
|
```
|
||||||
|
|
||||||
|
If everything works, you should see something like this:
|
||||||
|
![plots of the example/plots.rs animations](images/plots.png)
|
96
rust/pareen/examples/plots.rs
Normal file
96
rust/pareen/examples/plots.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use gnuplot::{AutoOption, AxesCommon, Color, Figure, LineWidth};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut plots = Plots { plots: Vec::new() };
|
||||||
|
|
||||||
|
plots.add("id", pareen::id());
|
||||||
|
plots.add("lerp between 2 and 4", pareen::lerp(2.0, 4.0));
|
||||||
|
plots.add(
|
||||||
|
"dynamic lerp between sin^2 and cos",
|
||||||
|
pareen::circle().sin().powi(2).lerp(pareen::circle().cos()),
|
||||||
|
);
|
||||||
|
plots.add(
|
||||||
|
"dynamic lerp, squeezed into [0.5 .. 1]",
|
||||||
|
pareen::circle()
|
||||||
|
.sin()
|
||||||
|
.powi(2)
|
||||||
|
.lerp(pareen::circle().cos())
|
||||||
|
.squeeze_and_surround(0.5..=1.0, 0.0),
|
||||||
|
);
|
||||||
|
plots.add(
|
||||||
|
"switch from 1 to 2 at time=0.5",
|
||||||
|
pareen::constant(1.0).switch(0.5, 2.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "easer")]
|
||||||
|
plots.add(
|
||||||
|
"ease transition from 2 to a proportional anim",
|
||||||
|
pareen::constant(2.0).seq_ease_in_out(
|
||||||
|
0.5,
|
||||||
|
easer::functions::Cubic,
|
||||||
|
0.3,
|
||||||
|
pareen::prop(1.0f32),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
plots.show_gnuplot();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(
|
||||||
|
n: usize,
|
||||||
|
max_t: f32,
|
||||||
|
anim: pareen::Anim<impl pareen::Fun<T = f32, V = f32>>,
|
||||||
|
) -> (Vec<f32>, Vec<f32>) {
|
||||||
|
let mut ts = Vec::new();
|
||||||
|
let mut vs = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..=n {
|
||||||
|
let time = i as f32 / n as f32 * max_t;
|
||||||
|
let value = anim.eval(time);
|
||||||
|
|
||||||
|
ts.push(time);
|
||||||
|
vs.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
(ts, vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Plot {
|
||||||
|
name: &'static str,
|
||||||
|
ts: Vec<f32>,
|
||||||
|
vs: Vec<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Plots {
|
||||||
|
plots: Vec<Plot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plots {
|
||||||
|
fn add(&mut self, name: &'static str, anim: pareen::Anim<impl pareen::Fun<T = f32, V = f32>>) {
|
||||||
|
let (ts, vs) = sample(1000, 1.0, anim);
|
||||||
|
|
||||||
|
self.plots.push(Plot { name, ts, vs });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_gnuplot(&self) {
|
||||||
|
let mut figure = Figure::new();
|
||||||
|
|
||||||
|
// Show plots in a square rows/columns layout
|
||||||
|
let n_cols = (self.plots.len() as f32).sqrt() as u32;
|
||||||
|
let n_rows = (self.plots.len() as f32).sqrt().ceil() as u32;
|
||||||
|
|
||||||
|
for (i, plot) in self.plots.iter().enumerate() {
|
||||||
|
figure
|
||||||
|
.axes2d()
|
||||||
|
.lines(&plot.ts, &plot.vs, &[Color("blue"), LineWidth(3.0)])
|
||||||
|
.set_title(&plot.name, &[])
|
||||||
|
.set_x_label("time", &[])
|
||||||
|
.set_y_label("value", &[])
|
||||||
|
.set_x_ticks(Some((AutoOption::Fix(0.5), 0)), &[], &[])
|
||||||
|
.set_y_ticks(Some((AutoOption::Fix(0.5), 0)), &[], &[])
|
||||||
|
.set_pos_grid(n_rows, n_cols, i as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure.show().unwrap();
|
||||||
|
}
|
||||||
|
}
|
BIN
rust/pareen/images/plots.png
Normal file
BIN
rust/pareen/images/plots.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
rust/pareen/images/seq_ease_in_out.png
Normal file
BIN
rust/pareen/images/seq_ease_in_out.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
841
rust/pareen/src/anim.rs
Normal file
841
rust/pareen/src/anim.rs
Normal file
@ -0,0 +1,841 @@
|
|||||||
|
use core::ops::{Add, Mul, RangeInclusive, Sub};
|
||||||
|
|
||||||
|
#[cfg(any(feature = "std", feature = "libm"))]
|
||||||
|
use num_traits::Float;
|
||||||
|
use num_traits::{float::FloatCore, Num, One, Zero};
|
||||||
|
|
||||||
|
use crate::{constant, fun, id};
|
||||||
|
|
||||||
|
/// A `Fun` represents anything that maps from some type `T` to another
|
||||||
|
/// type `V`.
|
||||||
|
///
|
||||||
|
/// `T` usually stands for time and `V` for some value that is parameterized by
|
||||||
|
/// time.
|
||||||
|
///
|
||||||
|
/// ## Implementation details
|
||||||
|
/// The only reason that we define this trait instead of just using `Fn(T) -> V`
|
||||||
|
/// is so that the library works in stable rust. Having this type allows us to
|
||||||
|
/// implement e.g. `std::ops::Add` for [`Anim<F>`](struct.Anim.html) where
|
||||||
|
/// `F: Fun`. Without this trait, it becomes difficult (impossible?) to provide
|
||||||
|
/// a name for `Add::Output`, unless you have the unstable feature
|
||||||
|
/// `type_alias_impl_trait` or `fn_traits`.
|
||||||
|
///
|
||||||
|
/// In contrast to `std::ops::FnOnce`, both input _and_ output are associated
|
||||||
|
/// types of `Fun`. The main reason is that this makes types smaller for the
|
||||||
|
/// user of the library. I have not observed any downsides to this yet.
|
||||||
|
pub trait Fun {
|
||||||
|
/// The function's input type. Usually time.
|
||||||
|
type T;
|
||||||
|
|
||||||
|
/// The function's output type.
|
||||||
|
type V;
|
||||||
|
|
||||||
|
/// Evaluate the function at time `t`.
|
||||||
|
fn eval(&self, t: Self::T) -> Self::V;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, F> Fun for &'a F
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = F::V;
|
||||||
|
|
||||||
|
fn eval(&self, t: Self::T) -> Self::V {
|
||||||
|
(*self).eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Anim` is the main type provided by pareen. It is a wrapper around any type
|
||||||
|
/// implementing [`Fun`](trait.Fun.html).
|
||||||
|
///
|
||||||
|
/// `Anim` provides methods that transform or compose animations, allowing
|
||||||
|
/// complex animations to be created out of simple pieces.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Anim<F>(pub F);
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
{
|
||||||
|
/// Evaluate the animation at time `t`.
|
||||||
|
pub fn eval(&self, t: F::T) -> F::V {
|
||||||
|
self.0.eval(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform an animation so that it applies a given function to its
|
||||||
|
/// values.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Turn `(2.0 * t)` into `(2.0 * t).sqrt() + 2.0 * t`:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::prop(2.0f32).map(|value| value.sqrt() + value);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), 2.0f32.sqrt() + 2.0);
|
||||||
|
/// ```
|
||||||
|
pub fn map<W>(self, f: impl Fn(F::V) -> W) -> Anim<impl Fun<T = F::T, V = W>> {
|
||||||
|
self.map_anim(fun(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform an animation so that it modifies time according to the given
|
||||||
|
/// function before evaluating the animation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Run an animation two times slower:
|
||||||
|
/// ```
|
||||||
|
/// let anim = pareen::cubic(&[1.0, 1.0, 1.0, 1.0]);
|
||||||
|
/// let slower_anim = anim.map_time(|t: f32| t / 2.0);
|
||||||
|
/// ```
|
||||||
|
pub fn map_time<S>(self, f: impl Fn(S) -> F::T) -> Anim<impl Fun<T = S, V = F::V>> {
|
||||||
|
fun(f).map_anim(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts from `Anim<F>` to `Anim<&F>`.
|
||||||
|
pub fn as_ref(&self) -> Anim<&F> {
|
||||||
|
Anim(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_anim<W, G, A>(self, anim: A) -> Anim<impl Fun<T = F::T, V = W>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::V, V = W>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
// Nested closures result in exponential compilation time increase, and we
|
||||||
|
// expect map_anim to be used often. Thus, we avoid using `pareen::fun` here.
|
||||||
|
// For reference: https://github.com/rust-lang/rust/issues/72408
|
||||||
|
Anim(MapClosure(self.0, anim.into().0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_time_anim<S, G, A>(self, anim: A) -> Anim<impl Fun<T = S, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = S, V = F::T>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
anim.into().map_anim(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_fn(self) -> impl Fn(F::T) -> F::V {
|
||||||
|
move |t| self.eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + Mul<Output = F::T>,
|
||||||
|
{
|
||||||
|
pub fn scale_time(self, t_scale: F::T) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map_time(move |t| t * t_scale.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Fun for Option<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = Option<F::V>;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Option<F::V> {
|
||||||
|
self.as_ref().map(|f| f.eval(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MapClosure<F, G>(F, G);
|
||||||
|
|
||||||
|
impl<F, G> Fun for MapClosure<F, G>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::V>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = G::V;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> G::V {
|
||||||
|
self.1.eval(self.0.eval(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, X, Y, F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun<T = T, V = (X, Y)>,
|
||||||
|
{
|
||||||
|
pub fn fst(self) -> Anim<impl Fun<T = F::T, V = X>> {
|
||||||
|
self.map(|(x, _)| x)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snd(self) -> Anim<impl Fun<T = F::T, V = Y>> {
|
||||||
|
self.map(|(_, y)| y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone,
|
||||||
|
{
|
||||||
|
/// Combine two animations into one, yielding an animation having pairs as
|
||||||
|
/// the values.
|
||||||
|
pub fn zip<G, A>(self, other: A) -> Anim<impl Fun<T = F::T, V = (F::V, G::V)>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
// Nested closures result in exponential compilation time increase, and we
|
||||||
|
// expect zip to be used frequently. Thus, we avoid using `pareen::fun` here.
|
||||||
|
// For reference: https://github.com/rust-lang/rust/issues/72408
|
||||||
|
Anim(ZipClosure(self.0, other.into().0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind<W, G>(self, f: impl Fn(F::V) -> Anim<G>) -> Anim<impl Fun<T = F::T, V = W>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = W>,
|
||||||
|
{
|
||||||
|
fun(move |t: F::T| f(self.eval(t.clone())).eval(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, V, F> Anim<F>
|
||||||
|
where
|
||||||
|
V: 'a + Copy,
|
||||||
|
F: Fun<T = T, V = &'a V> + 'a,
|
||||||
|
{
|
||||||
|
pub fn copied(self) -> Anim<impl Fun<T = T, V = V> + 'a> {
|
||||||
|
self.map(|x| *x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, V, F> Anim<F>
|
||||||
|
where
|
||||||
|
V: 'a + Clone,
|
||||||
|
F: Fun<T = T, V = &'a V> + 'a,
|
||||||
|
{
|
||||||
|
pub fn cloned(self) -> Anim<impl Fun<T = T, V = V> + 'a> {
|
||||||
|
self.map(|x| x.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ZipClosure<F, G>(F, G);
|
||||||
|
|
||||||
|
impl<F, G> Fun for ZipClosure<F, G>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = (F::V, G::V);
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Self::V {
|
||||||
|
(self.0.eval(t.clone()), self.1.eval(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + Sub<Output = F::T>,
|
||||||
|
{
|
||||||
|
/// Shift an animation in time, so that it is moved to the right by `t_delay`.
|
||||||
|
pub fn shift_time(self, t_delay: F::T) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
(id::<F::T, F::T>() - t_delay).map_anim(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + PartialOrd,
|
||||||
|
{
|
||||||
|
/// Concatenate `self` with another animation in time, using `self` until
|
||||||
|
/// time `self_end` (non-inclusive), and then switching to `next`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Switch from one constant value to another:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::constant(1.0f32).switch(0.5f32, 2.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0), 1.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(0.5), 2.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(42.0), 2.0);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Piecewise combinations of functions:
|
||||||
|
/// ```
|
||||||
|
/// let cubic_1 = pareen::cubic(&[4.4034, 0.0, -4.5455e-2, 0.0]);
|
||||||
|
/// let cubic_2 = pareen::cubic(&[-1.2642e1, 2.0455e1, -8.1364, 1.0909]);
|
||||||
|
/// let cubic_3 = pareen::cubic(&[1.6477e1, -4.9432e1, 4.7773e1, -1.3818e1]);
|
||||||
|
///
|
||||||
|
/// // Use cubic_1 for [0.0, 0.4), cubic_2 for [0.4, 0.8) and
|
||||||
|
/// // cubic_3 for [0.8, ..).
|
||||||
|
/// let anim = cubic_1.switch(0.4, cubic_2).switch(0.8, cubic_3);
|
||||||
|
/// ```
|
||||||
|
pub fn switch<G, A>(self, self_end: F::T, next: A) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
// Nested closures result in exponential compilation time increase, and we
|
||||||
|
// expect switch to be used frequently. Thus, we avoid using `pareen::fun` here.
|
||||||
|
// For reference: https://github.com/rust-lang/rust/issues/72408
|
||||||
|
cond(switch_cond(self_end), self, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play `self` in time range `range`, and `surround` outside of the time range.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::constant(10.0f32).surround(2.0..=5.0, 20.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0), 20.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(2.0), 10.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(4.0), 10.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(5.0), 10.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(6.0), 20.0);
|
||||||
|
/// ```
|
||||||
|
pub fn surround<G, A>(
|
||||||
|
self,
|
||||||
|
range: RangeInclusive<F::T>,
|
||||||
|
surround: A,
|
||||||
|
) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
// Nested closures result in exponential compilation time increase, and we
|
||||||
|
// expect surround to be used frequently. Thus, we avoid using `pareen::fun` here.
|
||||||
|
// For reference: https://github.com/rust-lang/rust/issues/72408
|
||||||
|
cond(surround_cond(range), self, surround)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_cond<T: PartialOrd>(self_end: T) -> Anim<impl Fun<T = T, V = bool>> {
|
||||||
|
fun(move |t| t < self_end)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surround_cond<T: PartialOrd>(range: RangeInclusive<T>) -> Anim<impl Fun<T = T, V = bool>> {
|
||||||
|
fun(move |t| range.contains(&t))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + PartialOrd,
|
||||||
|
F::V: Clone,
|
||||||
|
{
|
||||||
|
/// Play `self` until time `self_end`, then always return the value of
|
||||||
|
/// `self` at time `self_end`.
|
||||||
|
pub fn hold(self, self_end: F::T) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
let end_value = self.eval(self_end.clone());
|
||||||
|
|
||||||
|
self.switch(self_end, constant(end_value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + PartialOrd + Sub<Output = F::T>,
|
||||||
|
{
|
||||||
|
/// Play two animations in sequence, first playing `self` until time
|
||||||
|
/// `self_end` (non-inclusive), and then switching to `next`. Note that
|
||||||
|
/// `next` will see time starting at zero once it plays.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Stay at value `5.0` for ten seconds, then increase value proportionally:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim_1 = pareen::constant(5.0f32);
|
||||||
|
/// let anim_2 = pareen::prop(2.0f32) + 5.0;
|
||||||
|
/// let anim = anim_1.seq(10.0, anim_2);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0), 5.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(10.0), 5.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(11.0), 7.0);
|
||||||
|
/// ```
|
||||||
|
pub fn seq<G, A>(self, self_end: F::T, next: A) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.switch(self_end.clone(), next.into().shift_time(self_end))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seq_continue<G, A, H>(
|
||||||
|
self,
|
||||||
|
self_end: F::T,
|
||||||
|
next_fn: H,
|
||||||
|
) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
H: Fn(F::V) -> A,
|
||||||
|
{
|
||||||
|
let next = next_fn(self.eval(self_end.clone())).into();
|
||||||
|
|
||||||
|
self.seq(self_end, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + Sub<Output = F::T>,
|
||||||
|
{
|
||||||
|
/// Play an animation backwards, starting at time `end`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::prop(2.0f32).backwards(1.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), 2.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0f32), 0.0);
|
||||||
|
/// ```
|
||||||
|
pub fn backwards(self, end: F::T) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
(constant(end) - id()).map_anim(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy,
|
||||||
|
F::V: Copy + Num,
|
||||||
|
{
|
||||||
|
/// Given animation values in `[0.0 .. 1.0]`, this function transforms the
|
||||||
|
/// values so that they are in `[min .. max]`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let min = -3.0f32;
|
||||||
|
/// let max = 10.0;
|
||||||
|
/// let anim = pareen::id().scale_min_max(min, max);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), min);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0f32), max);
|
||||||
|
/// ```
|
||||||
|
pub fn scale_min_max(self, min: F::V, max: F::V) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self * (max.clone() - min) + min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "std", feature = "libm"))]
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::V: Float,
|
||||||
|
{
|
||||||
|
/// Apply `Float::sin` to the animation values.
|
||||||
|
pub fn sin(self) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map(Float::sin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply `Float::cos` to the animation values.
|
||||||
|
pub fn cos(self) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map(Float::cos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply `Float::powf` to the animation values.
|
||||||
|
pub fn powf(self, e: F::V) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map(move |v| v.powf(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::V: FloatCore,
|
||||||
|
{
|
||||||
|
/// Apply `FloatCore::abs` to the animation values.
|
||||||
|
pub fn abs(self) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map(FloatCore::abs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply `FloatCore::powi` to the animation values.
|
||||||
|
pub fn powi(self, n: i32) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map(move |v| v.powi(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + FloatCore,
|
||||||
|
{
|
||||||
|
/// Transform an animation in time, so that its time `[0 .. 1]` is shifted
|
||||||
|
/// and scaled into the given `range`.
|
||||||
|
///
|
||||||
|
/// In other words, this function can both delay and speed up or slow down a
|
||||||
|
/// given animation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Go from zero to 2π in half a second:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// // From zero to 2π in one second
|
||||||
|
/// let angle = pareen::circle::<f32, f32>();
|
||||||
|
///
|
||||||
|
/// // From zero to 2π in time range [0.5 .. 1.0]
|
||||||
|
/// let anim = angle.squeeze(0.5..=1.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.5), 0.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), std::f32::consts::PI * 2.0);
|
||||||
|
/// ```
|
||||||
|
pub fn squeeze(self, range: RangeInclusive<F::T>) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
let time_shift = *range.start();
|
||||||
|
let time_scale = F::T::one() / (*range.end() - *range.start());
|
||||||
|
|
||||||
|
self.map_time(move |t| (t - time_shift) * time_scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform an animation in time, so that its time `[0 .. 1]` is shifted
|
||||||
|
/// and scaled into the given `range`.
|
||||||
|
///
|
||||||
|
/// In other words, this function can both delay and speed up or slow down a
|
||||||
|
/// given animation.
|
||||||
|
///
|
||||||
|
/// For time outside of the given `range`, the `surround` animation is used
|
||||||
|
/// instead.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Go from zero to 2π in half a second:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// // From zero to 2π in one second
|
||||||
|
/// let angle = pareen::circle();
|
||||||
|
///
|
||||||
|
/// // From zero to 2π in time range [0.5 .. 1.0]
|
||||||
|
/// let anim = angle.squeeze_and_surround(0.5..=1.0, 42.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), 42.0f32);
|
||||||
|
/// assert_approx_eq!(anim.eval(0.5), 0.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), std::f32::consts::PI * 2.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.1), 42.0);
|
||||||
|
/// ```
|
||||||
|
pub fn squeeze_and_surround<G, A>(
|
||||||
|
self,
|
||||||
|
range: RangeInclusive<F::T>,
|
||||||
|
surround: A,
|
||||||
|
) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.squeeze(range.clone()).surround(range, surround)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play two animations in sequence, first playing `self` until time
|
||||||
|
/// `self_end` (non-inclusive), and then switching to `next`. The animations
|
||||||
|
/// are squeezed in time so that they fit into `[0 .. 1]` together.
|
||||||
|
///
|
||||||
|
/// `self` is played in time `[0 .. self_end)`, and then `next` is played
|
||||||
|
/// in time [self_end .. 1]`.
|
||||||
|
pub fn seq_squeeze<G, A>(self, self_end: F::T, next: A) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
let first = self.squeeze(Zero::zero()..=self_end);
|
||||||
|
let second = next.into().squeeze(self_end..=One::one());
|
||||||
|
|
||||||
|
first.switch(self_end, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repeat an animation forever.
|
||||||
|
pub fn repeat(self, period: F::T) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.map_time(move |t: F::T| (t * period.recip()).fract() * period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W, F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + Mul<W, Output = W>,
|
||||||
|
F::V: Copy + Add<W, Output = F::V> + Sub<Output = W>,
|
||||||
|
{
|
||||||
|
/// Linearly interpolate between two animations, starting at time zero and
|
||||||
|
/// finishing at time one.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Linearly interpolate between two constant values:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::constant(5.0f32).lerp(10.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), 5.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(0.5), 7.5);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), 10.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(2.0), 15.0);
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(any(feature = "std", feature = "libm"), doc = r##"
|
||||||
|
It is also possible to linearly interpolate between two non-constant
|
||||||
|
animations:
|
||||||
|
```
|
||||||
|
let anim = pareen::circle().sin().lerp(pareen::circle().cos());
|
||||||
|
let value: f32 = anim.eval(0.5f32);
|
||||||
|
```
|
||||||
|
"##)]
|
||||||
|
pub fn lerp<G, A>(self, other: A) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
let other = other.into();
|
||||||
|
|
||||||
|
fun(move |t| {
|
||||||
|
let a = self.eval(t);
|
||||||
|
let b = other.eval(t);
|
||||||
|
|
||||||
|
let delta = b - a;
|
||||||
|
|
||||||
|
a + t * delta
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun<V = Option<V>>,
|
||||||
|
F::T: Clone,
|
||||||
|
{
|
||||||
|
/// Unwrap an animation of optional values.
|
||||||
|
///
|
||||||
|
/// At any time, returns the animation value if it is not `None`, or the
|
||||||
|
/// given `default` value otherwise.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let anim1 = pareen::constant(Some(42)).unwrap_or(-1);
|
||||||
|
/// assert_eq!(anim1.eval(2), 42);
|
||||||
|
/// assert_eq!(anim1.eval(3), 42);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let cond = pareen::fun(|t| t % 2 == 0);
|
||||||
|
/// let anim1 = pareen::cond(cond, Some(42), None).unwrap_or(-1);
|
||||||
|
/// assert_eq!(anim1.eval(2), 42);
|
||||||
|
/// assert_eq!(anim1.eval(3), -1);
|
||||||
|
/// ```
|
||||||
|
pub fn unwrap_or<G, A>(self, default: A) -> Anim<impl Fun<T = F::T, V = V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.zip(default.into())
|
||||||
|
.map(|(v, default)| v.unwrap_or(default))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a function to the contained value (if any), or returns the
|
||||||
|
/// provided default (if not).
|
||||||
|
///
|
||||||
|
/// Note that the function `f` itself returns an animation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Animate a player's position offset if it is moving:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// fn my_offset_anim(
|
||||||
|
/// move_dir: Option<f32>,
|
||||||
|
/// ) -> pareen::Anim<impl pareen::Fun<T = f32, V = f32>> {
|
||||||
|
/// let move_speed = 2.0f32;
|
||||||
|
///
|
||||||
|
/// pareen::constant(move_dir).map_or(
|
||||||
|
/// 0.0,
|
||||||
|
/// move |move_dir| pareen::prop(move_dir) * move_speed,
|
||||||
|
/// )
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let move_anim = my_offset_anim(Some(1.0));
|
||||||
|
/// let stay_anim = my_offset_anim(None);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(move_anim.eval(0.5), 1.0);
|
||||||
|
/// assert_approx_eq!(stay_anim.eval(0.5), 0.0);
|
||||||
|
/// ```
|
||||||
|
pub fn map_or<W, G, H, A>(
|
||||||
|
self,
|
||||||
|
default: A,
|
||||||
|
f: impl Fn(V) -> Anim<H>,
|
||||||
|
) -> Anim<impl Fun<T = F::T, V = W>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = W>,
|
||||||
|
H: Fun<T = F::T, V = W>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
let default = default.into();
|
||||||
|
|
||||||
|
//self.bind(move |v| v.map_or(default, f))
|
||||||
|
|
||||||
|
fun(move |t: F::T| {
|
||||||
|
self.eval(t.clone())
|
||||||
|
.map_or_else(|| default.eval(t.clone()), |v| f(v).eval(t.clone()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of one of two animations depending on a condition.
|
||||||
|
///
|
||||||
|
/// This allows returning animations of different types conditionally.
|
||||||
|
///
|
||||||
|
/// Note that the condition `cond` may either be a value `true` and `false`, or
|
||||||
|
/// it may itself be a dynamic animation of type `bool`.
|
||||||
|
///
|
||||||
|
/// For dynamic conditions, in many cases it suffices to use either
|
||||||
|
/// [`Anim::switch`](struct.Anim.html#method.switch) or
|
||||||
|
/// [`Anim::seq`](struct.Anim.html#method.seq) instead of this function.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ## Constant conditions
|
||||||
|
///
|
||||||
|
/// The following example does _not_ compile, because the branches have
|
||||||
|
/// different types:
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let cond = true;
|
||||||
|
/// let anim = if cond { pareen::constant(1) } else { pareen::id() };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// However, this does compile:
|
||||||
|
/// ```
|
||||||
|
/// let cond = true;
|
||||||
|
/// let anim = pareen::cond(cond, 1, pareen::id());
|
||||||
|
///
|
||||||
|
/// assert_eq!(anim.eval(2), 1);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Dynamic conditions
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let cond = pareen::fun(|t| t * t <= 4);
|
||||||
|
/// let anim_1 = 1;
|
||||||
|
/// let anim_2 = pareen::id();
|
||||||
|
/// let anim = pareen::cond(cond, anim_1, anim_2);
|
||||||
|
///
|
||||||
|
/// assert_eq!(anim.eval(1), 1); // 1 * 1 <= 4
|
||||||
|
/// assert_eq!(anim.eval(2), 1); // 2 * 2 <= 4
|
||||||
|
/// assert_eq!(anim.eval(3), 3); // 3 * 3 > 4
|
||||||
|
/// ```
|
||||||
|
pub fn cond<F, G, H, Cond, A, B>(cond: Cond, a: A, b: B) -> Anim<impl Fun<T = F::T, V = G::V>>
|
||||||
|
where
|
||||||
|
F::T: Clone,
|
||||||
|
F: Fun<V = bool>,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
H: Fun<T = F::T, V = G::V>,
|
||||||
|
Cond: Into<Anim<F>>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
B: Into<Anim<H>>,
|
||||||
|
{
|
||||||
|
// Nested closures result in exponential compilation time increase, and we
|
||||||
|
// expect cond to be used often. Thus, we avoid using `pareen::fun` here.
|
||||||
|
// For reference: https://github.com/rust-lang/rust/issues/72408
|
||||||
|
Anim(CondClosure(cond.into().0, a.into().0, b.into().0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CondClosure<F, G, H>(F, G, H);
|
||||||
|
|
||||||
|
impl<F, G, H> Fun for CondClosure<F, G, H>
|
||||||
|
where
|
||||||
|
F::T: Clone,
|
||||||
|
F: Fun<V = bool>,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
H: Fun<T = F::T, V = G::V>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = G::V;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> G::V {
|
||||||
|
if self.0.eval(t.clone()) {
|
||||||
|
self.1.eval(t)
|
||||||
|
} else {
|
||||||
|
self.2.eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Linearly interpolate between two animations, starting at time zero and
|
||||||
|
/// finishing at time one.
|
||||||
|
///
|
||||||
|
/// This is a wrapper around [`Anim::lerp`](struct.Anim.html#method.lerp) for
|
||||||
|
/// convenience, allowing automatic conversion into [`Anim`](struct.Anim.html)
|
||||||
|
/// for both `a` and `b`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Linearly interpolate between two constant values:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::lerp(5.0f32, 10.0);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), 5.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(0.5), 7.5);
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), 10.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(2.0), 15.0);
|
||||||
|
/// ```
|
||||||
|
pub fn lerp<T, V, W, F, G, A, B>(a: A, b: B) -> Anim<impl Fun<T = T, V = V>>
|
||||||
|
where
|
||||||
|
T: Copy + Mul<W, Output = W>,
|
||||||
|
V: Copy + Add<W, Output = V> + Sub<Output = W>,
|
||||||
|
F: Fun<T = T, V = V>,
|
||||||
|
G: Fun<T = T, V = V>,
|
||||||
|
A: Into<Anim<F>>,
|
||||||
|
B: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
a.into().lerp(b.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an animation that depends on matching some expression.
|
||||||
|
///
|
||||||
|
/// Importantly, this macro allows returning animations of a different type in
|
||||||
|
/// each match arm, which is not possible with a normal `match` expression.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// enum MyPlayerState {
|
||||||
|
/// Standing,
|
||||||
|
/// Running,
|
||||||
|
/// Jumping,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn my_anim(state: MyPlayerState) -> pareen::Anim<impl pareen::Fun<T = f64, V = f64>> {
|
||||||
|
/// pareen::anim_match!(state;
|
||||||
|
/// MyPlayerState::Standing => pareen::constant(0.0),
|
||||||
|
/// MyPlayerState::Running => pareen::prop(1.0),
|
||||||
|
/// MyPlayerState::Jumping => pareen::id().powi(2),
|
||||||
|
/// )
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(my_anim(MyPlayerState::Standing).eval(2.0), 0.0);
|
||||||
|
/// assert_approx_eq!(my_anim(MyPlayerState::Running).eval(2.0), 2.0);
|
||||||
|
/// assert_approx_eq!(my_anim(MyPlayerState::Jumping).eval(2.0), 4.0);
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! anim_match {
|
||||||
|
(
|
||||||
|
$expr:expr;
|
||||||
|
$($pat:pat => $value:expr $(,)?)*
|
||||||
|
) => {
|
||||||
|
$crate::fun(move |t| match $expr {
|
||||||
|
$(
|
||||||
|
$pat => ($crate::Anim::from($value)).eval(t),
|
||||||
|
)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
51
rust/pareen/src/anim_box.rs
Normal file
51
rust/pareen/src/anim_box.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use core::ops::{Deref, Sub};
|
||||||
|
extern crate alloc;
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use crate::{Anim, Fun};
|
||||||
|
|
||||||
|
pub type AnimBox<T, V> = Anim<Box<dyn Fun<T = T, V = V>>>;
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun + 'static,
|
||||||
|
{
|
||||||
|
/// Returns a boxed version of this animation.
|
||||||
|
///
|
||||||
|
/// This may be used to reduce the compilation time of deeply nested
|
||||||
|
/// animations.
|
||||||
|
pub fn into_box(self) -> AnimBox<F::T, F::V> {
|
||||||
|
Anim(Box::new(self.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_box_fn(self) -> Box<dyn Fn(F::T) -> F::V> {
|
||||||
|
Box::new(self.into_fn())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We need to get rid of the 'static requirements.
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun + 'static,
|
||||||
|
F::T: Copy + PartialOrd + Sub<Output = F::T> + 'static,
|
||||||
|
F::V: 'static,
|
||||||
|
{
|
||||||
|
pub fn seq_box<G, A>(self, self_end: F::T, next: A) -> AnimBox<F::T, F::V>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V> + 'static,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.into_box()
|
||||||
|
.seq(self_end, next.into().into_box())
|
||||||
|
.into_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, V> Fun for Box<dyn Fun<T = T, V = V>> {
|
||||||
|
type T = T;
|
||||||
|
type V = V;
|
||||||
|
|
||||||
|
fn eval(&self, t: Self::T) -> Self::V {
|
||||||
|
self.deref().eval(t)
|
||||||
|
}
|
||||||
|
}
|
191
rust/pareen/src/anim_with_dur.rs
Normal file
191
rust/pareen/src/anim_with_dur.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use core::ops::{Add, Div, Mul, Sub};
|
||||||
|
|
||||||
|
use num_traits::{float::FloatCore, One};
|
||||||
|
|
||||||
|
use crate::{Anim, Fun};
|
||||||
|
|
||||||
|
/// An `Anim` together with the duration that it is intended to be played for.
|
||||||
|
///
|
||||||
|
/// Explicitly carrying the duration around makes it easier to sequentially
|
||||||
|
/// compose animations in some places.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AnimWithDur<F: Fun>(pub Anim<F>, pub F::T);
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
{
|
||||||
|
/// Tag this animation with the duration that it is intended to be played
|
||||||
|
/// for.
|
||||||
|
///
|
||||||
|
/// Note that using this tagging is completely optional, but it may
|
||||||
|
/// make it easier to combine animations sometimes.
|
||||||
|
pub fn dur(self, t: F::T) -> AnimWithDur<F> {
|
||||||
|
AnimWithDur(self, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, V> From<&'a [V]> for AnimWithDur<SliceClosure<'a, V>>
|
||||||
|
where
|
||||||
|
V: Clone,
|
||||||
|
{
|
||||||
|
fn from(slice: &'a [V]) -> Self {
|
||||||
|
AnimWithDur(Anim(SliceClosure(slice)), slice.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice<'a, V>(slice: &'a [V]) -> AnimWithDur<impl Fun<T = usize, V = V> + 'a>
|
||||||
|
where
|
||||||
|
V: Clone + 'a,
|
||||||
|
{
|
||||||
|
slice.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct SliceClosure<'a, V>(&'a [V]);
|
||||||
|
|
||||||
|
impl<'a, V> Fun for SliceClosure<'a, V>
|
||||||
|
where
|
||||||
|
V: Clone,
|
||||||
|
{
|
||||||
|
type T = usize;
|
||||||
|
type V = V;
|
||||||
|
|
||||||
|
fn eval(&self, t: Self::T) -> Self::V {
|
||||||
|
self.0[t].clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + FloatCore,
|
||||||
|
{
|
||||||
|
pub fn scale_to_dur(self, dur: F::T) -> AnimWithDur<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.scale_time(F::T::one() / dur).dur(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone,
|
||||||
|
{
|
||||||
|
pub fn as_ref(&self) -> AnimWithDur<&F> {
|
||||||
|
AnimWithDur(self.0.as_ref(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
{
|
||||||
|
pub fn transform<G, H>(self, h: H) -> AnimWithDur<G>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
H: FnOnce(Anim<F>) -> Anim<G>,
|
||||||
|
{
|
||||||
|
AnimWithDur(h(self.0), self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<W>(self, f: impl Fn(F::V) -> W) -> AnimWithDur<impl Fun<T = F::T, V = W>> {
|
||||||
|
self.transform(move |anim| anim.map(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dur(self, t: F::T) -> AnimWithDur<F> {
|
||||||
|
AnimWithDur(self.0, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, X, Y, F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
T: 'a + Clone,
|
||||||
|
X: 'a,
|
||||||
|
Y: 'a,
|
||||||
|
F: Fun<T = T, V = (X, Y)>,
|
||||||
|
{
|
||||||
|
pub fn unzip(
|
||||||
|
&'a self,
|
||||||
|
) -> (
|
||||||
|
AnimWithDur<impl Fun<T = F::T, V = X> + 'a>,
|
||||||
|
AnimWithDur<impl Fun<T = F::T, V = Y> + 'a>,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
self.as_ref().transform(|anim| anim.fst()),
|
||||||
|
self.as_ref().transform(|anim| anim.snd()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + PartialOrd + Sub<Output = F::T>,
|
||||||
|
{
|
||||||
|
pub fn seq<G>(self, next: Anim<G>) -> Anim<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
{
|
||||||
|
self.0.seq(self.1, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy + PartialOrd + Sub<Output = F::T> + Add<Output = F::T>,
|
||||||
|
{
|
||||||
|
pub fn seq_with_dur<G>(self, next: AnimWithDur<G>) -> AnimWithDur<impl Fun<T = F::T, V = F::V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = F::T, V = F::V>,
|
||||||
|
{
|
||||||
|
let dur = self.1.clone() + next.1;
|
||||||
|
AnimWithDur(self.seq(next.0), dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + FloatCore,
|
||||||
|
{
|
||||||
|
pub fn repeat(self) -> Anim<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
self.0.repeat(self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + Sub<Output = F::T>,
|
||||||
|
{
|
||||||
|
pub fn backwards(self) -> AnimWithDur<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
AnimWithDur(self.0.backwards(self.1.clone()), self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::T: Clone + Mul<Output = F::T> + Div<Output = F::T>,
|
||||||
|
{
|
||||||
|
pub fn scale_time(self, t_scale: F::T) -> AnimWithDur<impl Fun<T = F::T, V = F::V>> {
|
||||||
|
AnimWithDur(self.0.scale_time(t_scale.clone()), self.1 / t_scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! seq_with_dur {
|
||||||
|
(
|
||||||
|
$expr:expr $(,)?
|
||||||
|
) => {
|
||||||
|
$expr
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$head:expr,
|
||||||
|
$($tail:expr $(,)?)+
|
||||||
|
) => {
|
||||||
|
$head.seq_with_dur($crate::seq_with_dur!($($tail,)*))
|
||||||
|
}
|
||||||
|
}
|
169
rust/pareen/src/arithmetic.rs
Normal file
169
rust/pareen/src/arithmetic.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use core::ops::{Add, Mul, Neg, Sub};
|
||||||
|
|
||||||
|
use crate::{primitives::ConstantClosure, Anim, Fun};
|
||||||
|
|
||||||
|
impl<F, G> Add<Anim<G>> for Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::V: Add<G::V>,
|
||||||
|
{
|
||||||
|
type Output = Anim<AddClosure<F, G>>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Anim<G>) -> Self::Output {
|
||||||
|
Anim(AddClosure(self.0, rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W, F> Add<W> for Anim<F>
|
||||||
|
where
|
||||||
|
W: Copy,
|
||||||
|
F: Fun,
|
||||||
|
F::V: Add<W>,
|
||||||
|
{
|
||||||
|
type Output = Anim<AddClosure<F, ConstantClosure<F::T, W>>>;
|
||||||
|
|
||||||
|
fn add(self, rhs: W) -> Self::Output {
|
||||||
|
Anim(AddClosure(self.0, ConstantClosure::from(rhs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, G> Sub<Anim<G>> for Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::V: Sub<G::V>,
|
||||||
|
{
|
||||||
|
type Output = Anim<SubClosure<F, G>>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Anim<G>) -> Self::Output {
|
||||||
|
Anim(SubClosure(self.0, rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W, F> Sub<W> for Anim<F>
|
||||||
|
where
|
||||||
|
W: Copy,
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy,
|
||||||
|
F::V: Sub<W>,
|
||||||
|
{
|
||||||
|
type Output = Anim<SubClosure<F, ConstantClosure<F::T, W>>>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: W) -> Self::Output {
|
||||||
|
Anim(SubClosure(self.0, ConstantClosure::from(rhs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, G> Mul<Anim<G>> for Anim<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::T: Copy,
|
||||||
|
F::V: Mul<G::V>,
|
||||||
|
{
|
||||||
|
type Output = Anim<MulClosure<F, G>>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Anim<G>) -> Self::Output {
|
||||||
|
Anim(MulClosure(self.0, rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W, F> Mul<W> for Anim<F>
|
||||||
|
where
|
||||||
|
W: Copy,
|
||||||
|
F: Fun,
|
||||||
|
F::T: Copy,
|
||||||
|
F::V: Mul<W>,
|
||||||
|
{
|
||||||
|
type Output = Anim<MulClosure<F, ConstantClosure<F::T, W>>>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: W) -> Self::Output {
|
||||||
|
Anim(MulClosure(self.0, ConstantClosure::from(rhs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, F> Neg for Anim<F>
|
||||||
|
where
|
||||||
|
V: Copy,
|
||||||
|
F: Fun<V = V>,
|
||||||
|
{
|
||||||
|
type Output = Anim<NegClosure<F>>;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Anim(NegClosure(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AddClosure<F, G>(F, G);
|
||||||
|
|
||||||
|
impl<F, G> Fun for AddClosure<F, G>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::T: Clone,
|
||||||
|
F::V: Add<G::V>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = <F::V as Add<G::V>>::Output;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Self::V {
|
||||||
|
self.0.eval(t.clone()) + self.1.eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SubClosure<F, G>(F, G);
|
||||||
|
|
||||||
|
impl<F, G> Fun for SubClosure<F, G>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::T: Clone,
|
||||||
|
F::V: Sub<G::V>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = <F::V as Sub<G::V>>::Output;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Self::V {
|
||||||
|
self.0.eval(t.clone()) - self.1.eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MulClosure<F, G>(F, G);
|
||||||
|
|
||||||
|
impl<F, G> Fun for MulClosure<F, G>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
G: Fun<T = F::T>,
|
||||||
|
F::T: Copy,
|
||||||
|
F::V: Mul<G::V>,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = <F::V as Mul<G::V>>::Output;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Self::V {
|
||||||
|
self.0.eval(t) * self.1.eval(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct NegClosure<F>(F);
|
||||||
|
|
||||||
|
impl<F> Fun for NegClosure<F>
|
||||||
|
where
|
||||||
|
F: Fun,
|
||||||
|
F::V: Neg,
|
||||||
|
{
|
||||||
|
type T = F::T;
|
||||||
|
type V = <F::V as Neg>::Output;
|
||||||
|
|
||||||
|
fn eval(&self, t: F::T) -> Self::V {
|
||||||
|
-self.0.eval(t)
|
||||||
|
}
|
||||||
|
}
|
233
rust/pareen/src/easer_combinators.rs
Normal file
233
rust/pareen/src/easer_combinators.rs
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
use num_traits::Float;
|
||||||
|
|
||||||
|
use easer::functions::Easing;
|
||||||
|
|
||||||
|
use crate::{fun, Anim, Fun};
|
||||||
|
|
||||||
|
impl<V, F> Anim<F>
|
||||||
|
where
|
||||||
|
V: Float,
|
||||||
|
F: Fun<T = V, V = V>,
|
||||||
|
{
|
||||||
|
fn seq_ease<G, H, A>(
|
||||||
|
self,
|
||||||
|
self_end: V,
|
||||||
|
ease: impl Fn(V, V, V) -> Anim<G>,
|
||||||
|
ease_duration: V,
|
||||||
|
next: A,
|
||||||
|
) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
G: Fun<T = V, V = V>,
|
||||||
|
H: Fun<T = V, V = V>,
|
||||||
|
A: Into<Anim<H>>,
|
||||||
|
{
|
||||||
|
let next = next.into();
|
||||||
|
|
||||||
|
let ease_start_value = self.eval(self_end);
|
||||||
|
let ease_end_value = next.eval(V::zero());
|
||||||
|
let ease_delta = ease_end_value - ease_start_value;
|
||||||
|
let ease = ease(ease_start_value, ease_delta, ease_duration);
|
||||||
|
|
||||||
|
self.seq(self_end, ease).seq(self_end + ease_duration, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play two animations in sequence, transitioning between them with an
|
||||||
|
/// easing-in function from
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html).
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// The values of `self` at `self_end` and of `next` at time zero are used
|
||||||
|
/// to determine the parameters of the easing function.
|
||||||
|
///
|
||||||
|
/// Note that, as with [`seq`](struct.Anim.html#method.seq), the `next`
|
||||||
|
/// animation will see time starting at zero once it plays.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `self_end` - Time at which the `self` animation is to stop.
|
||||||
|
/// * `_easing` - A struct implementing
|
||||||
|
/// [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
/// This determines the easing function that will be used for the
|
||||||
|
/// transition. It is passed as a parameter here to simplify type
|
||||||
|
/// inference.
|
||||||
|
/// * `ease_duration` - The amount of time to use for transitioning to `next`.
|
||||||
|
/// * `next` - The animation to play after transitioning.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// See [`seq_ease_in_out`](struct.Anim.html#method.seq_ease_in_out) for an example.
|
||||||
|
pub fn seq_ease_in<E, G, A>(
|
||||||
|
self,
|
||||||
|
self_end: V,
|
||||||
|
_easing: E,
|
||||||
|
ease_duration: V,
|
||||||
|
next: A,
|
||||||
|
) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
E: Easing<V>,
|
||||||
|
G: Fun<T = V, V = V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.seq_ease(self_end, ease_in::<E, V>, ease_duration, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play two animations in sequence, transitioning between them with an
|
||||||
|
/// easing-out function from
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html).
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// The values of `self` at `self_end` and of `next` at time zero are used
|
||||||
|
/// to determine the parameters of the easing function.
|
||||||
|
///
|
||||||
|
/// Note that, as with [`seq`](struct.Anim.html#method.seq), the `next`
|
||||||
|
/// animation will see time starting at zero once it plays.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `self_end` - Time at which the `self` animation is to stop.
|
||||||
|
/// * `_easing` - A struct implementing
|
||||||
|
/// [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
/// This determines the easing function that will be used for the
|
||||||
|
/// transition. It is passed as a parameter here to simplify type
|
||||||
|
/// inference.
|
||||||
|
/// * `ease_duration` - The amount of time to use for transitioning to `next`.
|
||||||
|
/// * `next` - The animation to play after transitioning.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// See [`seq_ease_in_out`](struct.Anim.html#method.seq_ease_in_out) for an example.
|
||||||
|
pub fn seq_ease_out<E, G, A>(
|
||||||
|
self,
|
||||||
|
self_end: V,
|
||||||
|
_: E,
|
||||||
|
ease_duration: V,
|
||||||
|
next: A,
|
||||||
|
) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
E: Easing<V>,
|
||||||
|
G: Fun<T = V, V = V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.seq_ease(self_end, ease_out::<E, V>, ease_duration, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play two animations in sequence, transitioning between them with an
|
||||||
|
/// easing-in-out function from
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html).
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// The values of `self` at `self_end` and of `next` at time zero are used
|
||||||
|
/// to determine the parameters of the easing function.
|
||||||
|
///
|
||||||
|
/// Note that, as with [`seq`](struct.Anim.html#method.seq), the `next`
|
||||||
|
/// animation will see time starting at zero once it plays.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `self_end` - Time at which the `self` animation is to stop.
|
||||||
|
/// * `_easing` - A struct implementing
|
||||||
|
/// [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
/// This determines the easing function that will be used for the
|
||||||
|
/// transition. It is passed as a parameter here to simplify type
|
||||||
|
/// inference.
|
||||||
|
/// * `ease_duration` - The amount of time to use for transitioning to `next`.
|
||||||
|
/// * `next` - The animation to play after transitioning.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Play a constant value until time `0.5`, then transition for `0.3`
|
||||||
|
/// time units, using a cubic function, into a second animation:
|
||||||
|
/// ```
|
||||||
|
/// let first_anim = pareen::constant(2.0);
|
||||||
|
/// let second_anim = pareen::prop(1.0f32);
|
||||||
|
/// let anim = first_anim.seq_ease_in_out(
|
||||||
|
/// 0.5,
|
||||||
|
/// easer::functions::Cubic,
|
||||||
|
/// 0.3,
|
||||||
|
/// second_anim,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// The animation will look like this:
|
||||||
|
///
|
||||||
|
/// ![plot for seq_ease_in_out](https://raw.githubusercontent.com/leod/pareen/master/images/seq_ease_in_out.png)
|
||||||
|
pub fn seq_ease_in_out<E, G, A>(
|
||||||
|
self,
|
||||||
|
self_end: V,
|
||||||
|
_: E,
|
||||||
|
ease_duration: V,
|
||||||
|
next: A,
|
||||||
|
) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
E: Easing<V>,
|
||||||
|
G: Fun<T = V, V = V>,
|
||||||
|
A: Into<Anim<G>>,
|
||||||
|
{
|
||||||
|
self.seq_ease(self_end, ease_in_out::<E, V>, ease_duration, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integrate an easing-in function from the
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html) library.
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `start` - The start value for the easing function.
|
||||||
|
/// * `delta` - The change in the value from beginning to end time.
|
||||||
|
/// * `duration` - The total time between beginning and end.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
/// Documentation for [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
pub fn ease_in<E, V>(start: V, delta: V, duration: V) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
V: Float,
|
||||||
|
E: Easing<V>,
|
||||||
|
{
|
||||||
|
fun(move |t| E::ease_in(t, start, delta, duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integrate an easing-out function from the
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html) library.
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `start` - The start value for the easing function.
|
||||||
|
/// * `delta` - The change in the value from beginning to end time.
|
||||||
|
/// * `duration` - The total time between beginning and end.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
/// Documentation for [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
pub fn ease_out<E, V>(start: V, delta: V, duration: V) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
V: Float,
|
||||||
|
E: Easing<V>,
|
||||||
|
{
|
||||||
|
fun(move |t| E::ease_out(t, start, delta, duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integrate an easing-in-out function from the
|
||||||
|
/// [`easer`](https://docs.rs/easer/0.2.1/easer/index.html) library.
|
||||||
|
///
|
||||||
|
/// This is only available when enabling the `easer` feature for `pareen`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `start` - The start value for the easing function.
|
||||||
|
/// * `delta` - The change in the value from beginning to end time.
|
||||||
|
/// * `duration` - The total time between beginning and end.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
/// Documentation for [`easer::functions::Easing`](https://docs.rs/easer/0.2.1/easer/functions/trait.Easing.html).
|
||||||
|
pub fn ease_in_out<E, V>(start: V, delta: V, duration: V) -> Anim<impl Fun<T = V, V = V>>
|
||||||
|
where
|
||||||
|
V: Float,
|
||||||
|
E: Easing<V>,
|
||||||
|
{
|
||||||
|
fun(move |t| E::ease_in_out(t, start, delta, duration))
|
||||||
|
}
|
71
rust/pareen/src/lib.rs
Normal file
71
rust/pareen/src/lib.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
//! Pareen is a small library for *par*ameterized inbetw*een*ing.
|
||||||
|
//! The intended application is in game programming, where you sometimes have
|
||||||
|
//! two discrete game states between which you want to transition smoothly
|
||||||
|
//! for visualization purposes.
|
||||||
|
//!
|
||||||
|
//! Pareen gives you tools for composing animations that are parameterized by
|
||||||
|
//! time (i.e. mappings from time to some animated value) without constantly
|
||||||
|
//! having to pass around time variables; it hides the plumbing, so that you
|
||||||
|
//! need to provide time only once: when evaluating the animation.
|
||||||
|
//!
|
||||||
|
//! Animations are composed similarly to Rust's iterators, so no memory
|
||||||
|
//! allocations are necessary.
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use assert_approx_eq::assert_approx_eq;
|
||||||
|
//! // An animation returning a constant value
|
||||||
|
//! let anim1 = pareen::constant(1.0f64);
|
||||||
|
//!
|
||||||
|
//! // Animations can be evaluated at any time
|
||||||
|
//! let value = anim1.eval(0.5);
|
||||||
|
//!
|
||||||
|
//! // Animations can be played in sequence
|
||||||
|
//! let anim2 = anim1.seq(0.7, pareen::prop(0.25) + 0.5);
|
||||||
|
//!
|
||||||
|
#![cfg_attr(
|
||||||
|
any(feature = "std", feature = "libm"),
|
||||||
|
doc = r##"
|
||||||
|
// Animations can be composed and transformed in various ways
|
||||||
|
let anim3 = anim2
|
||||||
|
.lerp(pareen::circle().cos())
|
||||||
|
.scale_min_max(5.0, 10.0)
|
||||||
|
.backwards(1.0)
|
||||||
|
.squeeze(0.5..=1.0);
|
||||||
|
|
||||||
|
let anim4 = pareen::cubic(&[1.0, 2.0, 3.0, 4.0]) - anim3;
|
||||||
|
|
||||||
|
let value = anim4.eval(1.0);
|
||||||
|
assert_approx_eq!(value, 0.0);
|
||||||
|
"##
|
||||||
|
)]
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
mod anim;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
mod anim_box;
|
||||||
|
mod anim_with_dur;
|
||||||
|
mod arithmetic;
|
||||||
|
mod primitives;
|
||||||
|
|
||||||
|
pub mod stats;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "easer", any(feature = "std", feature = "libm")))]
|
||||||
|
mod easer_combinators;
|
||||||
|
|
||||||
|
pub use anim::{cond, lerp, Anim, Fun};
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub use anim_box::AnimBox;
|
||||||
|
pub use anim_with_dur::{slice, AnimWithDur};
|
||||||
|
pub use primitives::{
|
||||||
|
circle, constant, cubic, cycle, fun, half_circle, id, prop, quadratic, quarter_circle,
|
||||||
|
};
|
||||||
|
pub use stats::{simple_linear_regression, simple_linear_regression_with_slope};
|
||||||
|
|
||||||
|
#[cfg(all(feature = "easer", any(feature = "std", feature = "libm")))]
|
||||||
|
pub use easer;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "easer", any(feature = "std", feature = "libm")))]
|
||||||
|
pub use easer_combinators::{ease_in, ease_in_out, ease_out};
|
223
rust/pareen/src/primitives.rs
Normal file
223
rust/pareen/src/primitives.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::ops::Mul;
|
||||||
|
|
||||||
|
use crate::{Anim, Fun};
|
||||||
|
|
||||||
|
use num_traits::{FloatConst, float::FloatCore};
|
||||||
|
|
||||||
|
/// Turn any function `Fn(T) -> V` into an [`Anim`](struct.Anim.html).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// fn my_crazy_function(t: f32) -> f32 {
|
||||||
|
/// 42.0 / t
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let anim = pareen::fun(my_crazy_function);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(1.0), 42.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(2.0), 21.0);
|
||||||
|
/// ```
|
||||||
|
pub fn fun<T, V>(f: impl Fn(T) -> V) -> Anim<impl Fun<T = T, V = V>> {
|
||||||
|
From::from(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WrapFn<T, V, F: Fn(T) -> V>(F, PhantomData<(T, V)>);
|
||||||
|
|
||||||
|
impl<T, V, F> From<F> for Anim<WrapFn<T, V, F>>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> V,
|
||||||
|
{
|
||||||
|
fn from(f: F) -> Self {
|
||||||
|
Anim(WrapFn(f, PhantomData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, V, F> Fun for WrapFn<T, V, F>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> V,
|
||||||
|
{
|
||||||
|
type T = T;
|
||||||
|
type V = V;
|
||||||
|
|
||||||
|
fn eval(&self, t: T) -> V {
|
||||||
|
self.0(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A constant animation, always returning the same value.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::constant(1.0f32);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(-10000.0f32), 1.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0), 1.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(42.0), 1.0);
|
||||||
|
/// ```
|
||||||
|
pub fn constant<T, V: Clone>(c: V) -> Anim<impl Fun<T = T, V = V>> {
|
||||||
|
fun(move |_| c.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConstantClosure<T, V>(V, PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T, V> Fun for ConstantClosure<T, V>
|
||||||
|
where
|
||||||
|
V: Clone,
|
||||||
|
{
|
||||||
|
type T = T;
|
||||||
|
type V = V;
|
||||||
|
|
||||||
|
fn eval(&self, _: T) -> V {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, V> From<V> for ConstantClosure<T, V>
|
||||||
|
where
|
||||||
|
V: Clone,
|
||||||
|
{
|
||||||
|
fn from(v: V) -> Self {
|
||||||
|
ConstantClosure(v, PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, V> From<V> for Anim<ConstantClosure<T, V>>
|
||||||
|
where
|
||||||
|
V: Clone,
|
||||||
|
{
|
||||||
|
fn from(v: V) -> Self {
|
||||||
|
Anim(ConstantClosure::from(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An animation that returns a value proportional to time.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Scale time with a factor of three:
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::prop(3.0f32);
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0f32), 0.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(3.0), 9.0);
|
||||||
|
/// ```
|
||||||
|
pub fn prop<T, V, W>(m: V) -> Anim<impl Fun<T = T, V = W>>
|
||||||
|
where
|
||||||
|
V: Clone + Mul<Output = W> + From<T>,
|
||||||
|
{
|
||||||
|
fun(move |t| m.clone() * From::from(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An animation that returns time as its value.
|
||||||
|
///
|
||||||
|
/// This is the same as [`prop`](fn.prop.html) with a factor of one.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// let anim = pareen::id::<isize, isize>();
|
||||||
|
///
|
||||||
|
/// assert_eq!(anim.eval(-100), -100);
|
||||||
|
/// assert_eq!(anim.eval(0), 0);
|
||||||
|
/// assert_eq!(anim.eval(100), 100);
|
||||||
|
/// ```
|
||||||
|
/// ```
|
||||||
|
/// # use assert_approx_eq::assert_approx_eq;
|
||||||
|
/// let anim = pareen::id::<f32, f32>() * 3.0 + 4.0;
|
||||||
|
///
|
||||||
|
/// assert_approx_eq!(anim.eval(0.0), 4.0);
|
||||||
|
/// assert_approx_eq!(anim.eval(100.0), 304.0);
|
||||||
|
/// ```
|
||||||
|
pub fn id<T, V>() -> Anim<impl Fun<T = T, V = V>>
|
||||||
|
where
|
||||||
|
V: From<T>,
|
||||||
|
{
|
||||||
|
fun(From::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proportionally increase value from zero to 2π.
|
||||||
|
pub fn circle<T, V>() -> Anim<impl Fun<T = T, V = V>>
|
||||||
|
where
|
||||||
|
T: FloatCore,
|
||||||
|
V: FloatCore + FloatConst + From<T>,
|
||||||
|
{
|
||||||
|
prop(V::PI() * (V::one() + V::one()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proportionally increase value from zero to π.
|
||||||
|
pub fn half_circle<T, V>() -> Anim<impl Fun<T = T, V = V>>
|
||||||
|
where
|
||||||
|
T: FloatCore,
|
||||||
|
V: FloatCore + FloatConst + From<T>,
|
||||||
|
{
|
||||||
|
prop(V::PI())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proportionally increase value from zero to π/2.
|
||||||
|
pub fn quarter_circle<T, V>() -> Anim<impl Fun<T = T, V = V>>
|
||||||
|
where
|
||||||
|
T: FloatCore,
|
||||||
|
V: FloatCore + FloatConst + From<T>,
|
||||||
|
{
|
||||||
|
prop(V::PI() * (V::one() / (V::one() + V::one())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a quadratic polynomial in time.
|
||||||
|
pub fn quadratic<T>(w: &[T; 3]) -> Anim<impl Fun<T = T, V = T> + '_>
|
||||||
|
where
|
||||||
|
T: FloatCore,
|
||||||
|
{
|
||||||
|
fun(move |t| {
|
||||||
|
let t2 = t * t;
|
||||||
|
|
||||||
|
w[0] * t2 + w[1] * t + w[2]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a cubic polynomial in time.
|
||||||
|
pub fn cubic<T>(w: &[T; 4]) -> Anim<impl Fun<T = T, V = T> + '_>
|
||||||
|
where
|
||||||
|
T: FloatCore,
|
||||||
|
{
|
||||||
|
fun(move |t| {
|
||||||
|
let t2 = t * t;
|
||||||
|
let t3 = t2 * t;
|
||||||
|
|
||||||
|
w[0] * t3 + w[1] * t2 + w[2] * t + w[3]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count from 0 to `end` (non-inclusive) cyclically, at the given frames per
|
||||||
|
/// second rate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let anim = pareen::cycle(3, 5.0);
|
||||||
|
/// assert_eq!(anim.eval(0.0), 0);
|
||||||
|
/// assert_eq!(anim.eval(0.1), 0);
|
||||||
|
/// assert_eq!(anim.eval(0.3), 1);
|
||||||
|
/// assert_eq!(anim.eval(0.5), 2);
|
||||||
|
/// assert_eq!(anim.eval(0.65), 0);
|
||||||
|
///
|
||||||
|
/// assert_eq!(anim.eval(-0.1), 2);
|
||||||
|
/// assert_eq!(anim.eval(-0.3), 1);
|
||||||
|
/// assert_eq!(anim.eval(-0.5), 0);
|
||||||
|
/// ```
|
||||||
|
pub fn cycle(end: usize, fps: f32) -> Anim<impl Fun<T = f32, V = usize>> {
|
||||||
|
fun(move |t: f32| {
|
||||||
|
if t < 0.0 {
|
||||||
|
let tau = (t.abs() * fps) as usize;
|
||||||
|
|
||||||
|
end - 1 - tau % end
|
||||||
|
} else {
|
||||||
|
let tau = (t * fps) as usize;
|
||||||
|
|
||||||
|
tau % end
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
133
rust/pareen/src/stats.rs
Normal file
133
rust/pareen/src/stats.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use core::ops::{Add, Div, Mul};
|
||||||
|
|
||||||
|
use num_traits::{float::FloatCore, AsPrimitive, Zero};
|
||||||
|
|
||||||
|
use crate::{Anim, AnimWithDur, Fun};
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun<T = usize>,
|
||||||
|
{
|
||||||
|
pub fn fold<B, G>(&self, init: B, mut f: G) -> B
|
||||||
|
where
|
||||||
|
G: FnMut(B, F::V) -> B,
|
||||||
|
{
|
||||||
|
let mut b = init;
|
||||||
|
|
||||||
|
for t in 0..self.1 {
|
||||||
|
b = f(b, self.0.eval(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun<T = usize>,
|
||||||
|
F::V: Add<Output = F::V> + Zero,
|
||||||
|
{
|
||||||
|
pub fn sum(&self) -> F::V {
|
||||||
|
self.fold(Zero::zero(), |a, b| a + b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> AnimWithDur<F>
|
||||||
|
where
|
||||||
|
F: Fun<T = usize>,
|
||||||
|
F::T: Clone,
|
||||||
|
F::V: 'static + Add<Output = F::V> + Div<Output = F::V> + Zero + Copy,
|
||||||
|
usize: AsPrimitive<F::V>,
|
||||||
|
{
|
||||||
|
pub fn mean(&self) -> F::V {
|
||||||
|
let len = self.1.clone().as_();
|
||||||
|
self.sum() / len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Line<V> {
|
||||||
|
pub y_intercept: V,
|
||||||
|
pub slope: V,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> Fun for Line<V>
|
||||||
|
where
|
||||||
|
V: Add<Output = V> + Mul<Output = V> + Clone,
|
||||||
|
{
|
||||||
|
type T = V;
|
||||||
|
type V = V;
|
||||||
|
|
||||||
|
fn eval(&self, t: V) -> V {
|
||||||
|
self.y_intercept.clone() + self.slope.clone() * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_linear_regression_with_slope<V, F, A>(slope: V, values: A) -> Anim<Line<V>>
|
||||||
|
where
|
||||||
|
V: 'static + FloatCore + Copy,
|
||||||
|
F: Fun<T = usize, V = (V, V)>,
|
||||||
|
A: Into<AnimWithDur<F>>,
|
||||||
|
usize: AsPrimitive<V>,
|
||||||
|
{
|
||||||
|
// https://en.wikipedia.org/wiki/Simple_linear_regression#Fitting_the_regression_line
|
||||||
|
let values = values.into();
|
||||||
|
let (x, y) = values.unzip();
|
||||||
|
let x_mean = x.as_ref().mean();
|
||||||
|
let y_mean = y.as_ref().mean();
|
||||||
|
|
||||||
|
let y_intercept = y_mean - slope * x_mean;
|
||||||
|
|
||||||
|
Anim(Line { y_intercept, slope })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_linear_regression<V, F, A>(values: A) -> Anim<Line<V>>
|
||||||
|
where
|
||||||
|
V: 'static + FloatCore + Copy,
|
||||||
|
F: Fun<T = usize, V = (V, V)>,
|
||||||
|
A: Into<AnimWithDur<F>>,
|
||||||
|
usize: AsPrimitive<V>,
|
||||||
|
{
|
||||||
|
// https://en.wikipedia.org/wiki/Simple_linear_regression#Fitting_the_regression_line
|
||||||
|
let values = values.into();
|
||||||
|
let (x, y) = values.unzip();
|
||||||
|
let x_mean = x.as_ref().mean();
|
||||||
|
let y_mean = y.as_ref().mean();
|
||||||
|
let numerator = values
|
||||||
|
.as_ref()
|
||||||
|
.map(|(x, y)| (x - x_mean) * (y - y_mean))
|
||||||
|
.sum();
|
||||||
|
let denominator = x.as_ref().map(|x| (x - x_mean) * (x - x_mean)).sum();
|
||||||
|
let slope = numerator / denominator;
|
||||||
|
|
||||||
|
let y_intercept = y_mean - slope * x_mean;
|
||||||
|
|
||||||
|
Anim(Line { y_intercept, slope })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "alloc"))]
|
||||||
|
mod tests {
|
||||||
|
use assert_approx_eq::assert_approx_eq;
|
||||||
|
extern crate alloc;
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
use super::simple_linear_regression;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_perfect_regression() {
|
||||||
|
let straight_line = vec![(1.0, 1.0), (2.0, 2.0)];
|
||||||
|
let line = simple_linear_regression(straight_line.as_slice());
|
||||||
|
assert_approx_eq!(line.eval(1.0), 1.0f64);
|
||||||
|
assert_approx_eq!(line.eval(10.0), 10.0);
|
||||||
|
assert_approx_eq!(line.eval(-10.0), -10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_negative_perfect_regression() {
|
||||||
|
let straight_line = vec![(1.0, -1.0), (2.0, -2.0)];
|
||||||
|
let line = simple_linear_regression(straight_line.as_slice());
|
||||||
|
assert_approx_eq!(line.eval(1.0), -1.0f64);
|
||||||
|
assert_approx_eq!(line.eval(10.0), -10.0);
|
||||||
|
assert_approx_eq!(line.eval(-10.0), 10.0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user