diff options
Diffstat (limited to 'benoit-cli/src/keyframe')
-rw-r--r-- | benoit-cli/src/keyframe/keyframe/interpolate_between.rs | 165 | ||||
-rw-r--r-- | benoit-cli/src/keyframe/keyframe/mod.rs | 99 | ||||
-rw-r--r-- | benoit-cli/src/keyframe/keyframe_interpolater/mod.rs | 95 | ||||
-rw-r--r-- | benoit-cli/src/keyframe/mod.rs | 29 |
4 files changed, 388 insertions, 0 deletions
diff --git a/benoit-cli/src/keyframe/keyframe/interpolate_between.rs b/benoit-cli/src/keyframe/keyframe/interpolate_between.rs new file mode 100644 index 0000000..42e8c3e --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe/interpolate_between.rs @@ -0,0 +1,165 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::keyframe::{Keyframe, KeyframeInterpolater}; + +use benoit::{log, PRECISION}; +use benoit::complex::Complex; +use num_traits::Num; +use rug::Float; +use rug::ops::PowAssign; + +impl Keyframe { + /// Interpolates between `self` and the given other keyframe. + /// + /// The returned interpolater can be used as an iterator. + /// + /// The way in which the values are interpolated differ: + /// The centre, seed, and colour range values are interpreted as linear functions of the form (*a* · *b* + *c*). + /// The zoom value, however, is interpreted as a growth function of the form (*a*<sup>*x*</sup> · *b*). + /// + /// # Panics + /// + /// Panics if frame indices don't match. + + #[must_use] + pub fn interpolate_between(&self, other: &Self, frame_count: u32) -> KeyframeInterpolater { + assert!(self.frame <= other.frame, "starting frame ({}) is after stopping frame ({})", self.frame, other.frame); + assert!(other.frame < frame_count, "stopping frame ({}) is after last frame ({})", other.frame, frame_count - 0x1); + + let first_possible_frame = 0x0; + let last_possible_frame = frame_count - 0x1; + + let max_iter_count_step = linear_step( + (first_possible_frame, i64::try_from(self.max_iter_count).unwrap()), + (last_possible_frame, i64::try_from(other.max_iter_count).unwrap()), + ); + + let centre_step = linear_step_complex( (first_possible_frame, &self.centre), (last_possible_frame, &other.centre)); + let seed_step = linear_step_complex( (first_possible_frame, &self.seed), (last_possible_frame, &other.seed)); + let zoom_factor = growth_factor_bigfloat((first_possible_frame, &self.zoom), (last_possible_frame, &other.zoom)); + let colour_range_step = linear_step( (first_possible_frame, self.colour_range), (last_possible_frame, other.colour_range)); + + let mut interpolator = KeyframeInterpolater { + frame: Some(self.frame), + last_frame: other.frame, + + max_iter_count: self.max_iter_count, + centre: self.centre.clone(), + seed: self.seed.clone(), + zoom: self.zoom.clone(), + colour_range: self.colour_range, + + max_iter_count_step, + centre_step, + seed_step, + zoom_factor, + colour_range_step, + }; + + // Skip to the starting frame: + for _ in 0x0..self.frame { + interpolator.advance_values(); + } + + log!(value, interpolator); + interpolator + } +} + +#[must_use] +fn linear_step<T: Default + From<u32> + Num>((t0, f0): (u32, T), (t1, f1): (u32, T)) -> T { + if t1 == t0 || f1 == f0 { return Default::default() } + + // A linear function `f` of time `t` can be defined + // as: + // + // f(t) = at+b + // + // The starting value `b` is already known. We use + // the following expression to get the slope coef- + // ficient `a`: + // + // a = (t1-t0)/(t1-t0) + // + // wherein f1 and f0 are the last and first results + // of `f` - respectively - and t1 and t0 are like- + // wise the last and first timepoints. + + (f1 - f0) / (T::from(t1) - T::from(t0)) +} + +#[must_use] +fn linear_step_bigfloat((t0, f0): (u32, &Float), (t1, f1): (u32, &Float)) -> Float { + if t1 == t0 || f1 == f0 { return Float::new(PRECISION) } + + let denominator = f64::from(t1) - f64::from(t0); + + let numerator = Float::with_val(PRECISION, f1 - f0); + numerator / denominator +} + +#[must_use] +fn linear_step_complex((t0, f0): (u32, &Complex), (t1, f1): (u32, &Complex)) -> Complex { + let real_step = linear_step_bigfloat((t0, &f0.real), (t1, &f1.real)); + let imag_step = linear_step_bigfloat((t0, &f0.imag), (t1, &f1.imag)); + + Complex { real: real_step, imag: imag_step } +} + +#[must_use] +fn growth_factor_bigfloat((t0, f0): (u32, &Float), (t1, f1): (u32, &Float)) -> Float { + if t1 == t0 || f1 == f0 { return Float::with_val(PRECISION, 0x1) } + + // The growth function `f` of time `t` is: + // + // f(t) = b*a^t + // + // wherein `b` is the starting value and `a` is the + // growth factor. + // + // To isolate `a` we use: + // + // a = nroot(t1-t0,f1/f0) + // + // wherein `t0`, `t1`, `f1`, and `f0` are the first + // and last values on the two dimensions (`t` and + // `f`), altough in theory arbitrary. + // + // Because the following is true: + // + // nroot(n,m) = m^(1/n) + // + // the expression for `a` is simplified for use + // with Rug: + // + // a = (t1/t0)^(1/(f1-f0)) + + let exponent = 1.0 / (f64::from(t1) - f64::from(t0)); + + let mut factor = Float::with_val(PRECISION, f1 / f0); + factor.pow_assign(exponent); + + factor +} diff --git a/benoit-cli/src/keyframe/keyframe/mod.rs b/benoit-cli/src/keyframe/keyframe/mod.rs new file mode 100644 index 0000000..9a4e84a --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe/mod.rs @@ -0,0 +1,99 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +mod interpolate_between; + +use crate::error::Error; + +use benoit::PRECISION; +use benoit::complex::Complex; +use rug::Float; + +/// Defines a single keyframe for animating. +/// +/// Keyframes can be interpolated using the [`interpolate_between`](Keyframe::interpolate_between) method. +#[derive(Clone, Debug)] +pub struct Keyframe { + pub frame: u32, + pub max_iter_count: u64, + pub centre: Complex, + pub seed: Complex, + pub zoom: Float, + pub colour_range: f64, +} + +impl Keyframe { + /// Validates `self`, wrapping it in a [`Result`]. + /// + /// The following requirements must be upheld: + /// + /// * The maximum iteration count must be non-zero and less than or equal to `9223372036854775807`; + /// * The zoom value must be positive and non-zero; + /// * The colour range must be positive and non-zero; + /// + /// If these are upheld, an [`Ok`] object is returned. + /// + /// Do note that an "invalid" keyframe does not inherintly result in undefined behaviour if used. + /// + /// # Errors + /// + /// Yields an error if the keyframe could not be validated. + pub fn validate(&self) -> Result<(), Error> { + macro_rules! test { + ($assertion:expr, $($message:tt)*) => {{ + if !($assertion as bool) { + return Err(Error::InvalidKeyframe { message: format!($($message)?) }) + } + }}; + } + + test!(self.max_iter_count != 0x0, "max. iter. count ({}) cannot be zero", self.max_iter_count); + + // This is also tested by Config, but we don't know + // if this keyframe even came from a configuration. + test!( + self.max_iter_count <= u64::try_from(i64::MAX).unwrap(), + "max. iter. count ({}) cannot be greater than ({})", + self.max_iter_count, + i64::MAX, + ); + + test!(self.zoom > 0x0, "zoom ({}) must be greater than zero", self.zoom); + + test!(self.colour_range > 0.0, "colour range ({}) must be positive and non-zero", self.colour_range); + + Ok(()) + } +} + +impl Default for Keyframe { + fn default() -> Self { Self { + frame: 0x0, + max_iter_count: 0x100, + centre: Complex::new(PRECISION), + seed: Complex::new(PRECISION), + zoom: Float::with_val(PRECISION, 0x1), + colour_range: 256.0, + } } +} diff --git a/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs b/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs new file mode 100644 index 0000000..3faa546 --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs @@ -0,0 +1,95 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::keyframe::Keyframe; + +use benoit::complex::Complex; +use rug::Float; + +/// Interpolater between keyframes. +/// +/// This is done as an iterator over the interpolated values. +#[derive(Clone, Debug)] +pub struct KeyframeInterpolater { + pub(in super) frame: Option<u32>, + pub(in super) last_frame: u32, + + pub(in super) max_iter_count: u64, + pub(in super) centre: Complex, + pub(in super) seed: Complex, + pub(in super) zoom: Float, + pub(in super) colour_range: f64, + + pub(in super) max_iter_count_step: i64, + pub(in super) centre_step: Complex, + pub(in super) seed_step: Complex, + pub(in super) zoom_factor: Float, + pub(in super) colour_range_step: f64, +} + +impl KeyframeInterpolater { + /// Advances the contained values to the next step. + /// + /// # Panics + /// + /// Panics if overflow occurs when calculating the new maximum iteration count. + /// This is guaranteed to not happen as long as the iterator hasn't been completed. + pub(in super) fn advance_values(&mut self) { + self.max_iter_count = self.max_iter_count + .checked_add_signed(self.max_iter_count_step) + .unwrap(); + + self.centre.real += &self.centre_step.real; + self.centre.imag += &self.centre_step.imag; + self.seed.real += &self.seed_step.real; + self.seed.imag += &self.seed_step.imag; + self.zoom *= &self.zoom_factor; + self.colour_range += self.colour_range_step; + } +} + +impl Iterator for KeyframeInterpolater { + type Item = Keyframe; + + fn next(&mut self) -> Option<Self::Item> { + let frame = self.frame?; + assert!(frame <= self.last_frame); + + let keyframe = Keyframe { + frame, + max_iter_count: self.max_iter_count, + centre: self.centre.clone(), + seed: self.seed.clone(), + zoom: self.zoom.clone(), + colour_range: self.colour_range, + }; + + self.frame = (frame != self.last_frame).then(|| { + self.advance_values(); + frame + 0x1 + }); + + Some(keyframe) + } +} diff --git a/benoit-cli/src/keyframe/mod.rs b/benoit-cli/src/keyframe/mod.rs new file mode 100644 index 0000000..a4c722a --- /dev/null +++ b/benoit-cli/src/keyframe/mod.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +//! Animation keyframes. + +use crate::use_mod; +use_mod!(pub, keyframe); +use_mod!(pub, keyframe_interpolater); |