summaryrefslogtreecommitdiff
path: root/benoit-cli/src/keyframe
diff options
context:
space:
mode:
Diffstat (limited to 'benoit-cli/src/keyframe')
-rw-r--r--benoit-cli/src/keyframe/keyframe/interpolate_between.rs165
-rw-r--r--benoit-cli/src/keyframe/keyframe/mod.rs99
-rw-r--r--benoit-cli/src/keyframe/keyframe_interpolater/mod.rs95
-rw-r--r--benoit-cli/src/keyframe/mod.rs29
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* &middot; *b* + *c*).
+ /// The zoom value, however, is interpreted as a growth function of the form (*a*<sup>*x*</sup> &middot; *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);