use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Asset, Reflect, Clone, Debug, Serialize, Deserialize)]
pub struct SequenceAsset {
pub name: String,
pub shots: Vec<Shot>,
#[serde(default)]
pub markers: Vec<Marker>,
#[serde(default)]
pub blend_out: Option<Blend>,
}
impl SequenceAsset {
pub fn empty(name: impl Into<String>) -> Self {
Self {
name: name.into(),
shots: Vec::new(),
markers: Vec::new(),
blend_out: None,
}
}
pub fn duration(&self) -> f32 {
self.shots
.iter()
.map(|s| s.start + s.duration)
.fold(0.0, f32::max)
}
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub struct Shot {
pub start: f32,
pub duration: f32,
#[serde(default)]
pub blend_in: Option<Blend>,
pub rig: Rig,
#[serde(default)]
pub look: Look,
#[serde(default)]
pub lens: Lens,
#[serde(default)]
pub shake: Option<Shake>,
}
#[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Shake {
pub amplitude_deg: f32,
pub frequency_hz: f32,
#[serde(default)]
pub seed: u32,
#[serde(default)]
pub pos_amplitude: f32,
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub enum Rig {
Keys {
keys: Vec<Key>,
#[serde(default)]
interp: KeyInterp,
},
Rail {
points: Vec<Vec3>,
#[serde(default)]
kind: RailKind,
#[serde(default = "yes")]
constant_speed: bool,
#[serde(default)]
progress: ScalarTrack,
},
Orbit {
center: TargetRef,
radius: ScalarTrack,
#[serde(default)]
yaw_deg: ScalarTrack,
#[serde(default)]
pitch_deg: ScalarTrack,
},
}
fn yes() -> bool {
true
}
#[derive(Reflect, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyInterp {
#[default]
Eased,
CatmullRom,
}
#[derive(Reflect, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum RailKind {
#[default]
CatmullRom,
BSpline,
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub enum TargetRef {
Point(Vec3),
Entity(String),
}
#[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Key {
pub time: f32,
pub pos: Vec3,
#[serde(default)]
pub rot: Option<Quat>,
#[serde(default = "linear")]
pub ease: EaseFunction,
}
fn linear() -> EaseFunction {
EaseFunction::Linear
}
#[derive(Reflect, Clone, Debug, Default, Serialize, Deserialize)]
pub enum Look {
#[default]
Free,
At {
target: TargetRef,
#[serde(default)]
damping: Option<f32>,
},
Velocity,
}
#[derive(Reflect, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ScalarTrack {
pub keys: Vec<ScalarKey>,
}
#[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct ScalarKey {
pub time: f32,
pub value: f32,
#[serde(default = "linear")]
pub ease: EaseFunction,
}
impl ScalarTrack {
pub fn constant(value: f32) -> Self {
Self {
keys: vec![ScalarKey {
time: 0.0,
value,
ease: EaseFunction::Linear,
}],
}
}
pub fn sample(&self, t: f32) -> f32 {
let keys = &self.keys;
let Some(first) = keys.first() else {
return 0.0;
};
if t <= first.time {
return first.value;
}
for pair in keys.windows(2) {
let (a, b) = (&pair[0], &pair[1]);
if t <= b.time {
let span = (b.time - a.time).max(f32::EPSILON);
let w = a.ease.sample_clamped((t - a.time) / span);
return a.value + (b.value - a.value) * w;
}
}
keys.last().map_or(0.0, |k| k.value)
}
}
#[derive(Reflect, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Lens {
#[serde(default)]
pub fov: FovSpec,
#[serde(default)]
pub focus: Option<FocusTrack>,
#[serde(default)]
pub aperture_f_stops: Option<ScalarTrack>,
#[serde(default)]
pub exposure_ev100: Option<ScalarTrack>,
#[serde(default)]
pub dof_mode: DofMode,
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub enum FovSpec {
VerticalFovDeg(ScalarTrack),
FocalLengthMm {
track: ScalarTrack,
#[serde(default)]
filmback: Filmback,
},
}
impl Default for FovSpec {
fn default() -> Self {
FovSpec::VerticalFovDeg(ScalarTrack::constant(45.0))
}
}
#[derive(Reflect, Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Filmback {
#[default]
Super35,
FullFrame,
Super16,
Custom {
sensor_height_mm: f32,
},
}
impl Filmback {
pub fn sensor_height_mm(&self) -> f32 {
match self {
Filmback::Super35 => 18.66,
Filmback::FullFrame => 24.0,
Filmback::Super16 => 7.41,
Filmback::Custom { sensor_height_mm } => *sensor_height_mm,
}
}
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub enum FocusTrack {
Distance(ScalarTrack),
Target {
target: TargetRef,
#[serde(default)]
offset: f32,
},
}
#[derive(Reflect, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DofMode {
#[default]
Gaussian,
Bokeh,
}
#[derive(Reflect, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Blend {
pub secs: f32,
pub ease: EaseFunction,
}
#[derive(Reflect, Clone, Debug, Serialize, Deserialize)]
pub struct Marker {
pub time: f32,
pub name: String,
}
impl Key {
pub fn at(time: f32) -> Self {
Self {
time,
pos: Vec3::ZERO,
rot: None,
ease: EaseFunction::Linear,
}
}
pub fn pos(mut self, pos: Vec3) -> Self {
self.pos = pos;
self
}
pub fn rot(mut self, rot: Quat) -> Self {
self.rot = Some(rot);
self
}
pub fn looking_at(mut self, target: Vec3) -> Self {
self.rot = Some(
Transform::from_translation(self.pos)
.looking_at(target, Vec3::Y)
.rotation,
);
self
}
pub fn ease(mut self, ease: EaseFunction) -> Self {
self.ease = ease;
self
}
}
impl Shot {
pub fn keys(start: f32, duration: f32, keys: impl Into<Vec<Key>>) -> Self {
Self {
start,
duration,
blend_in: None,
rig: Rig::Keys {
keys: keys.into(),
interp: KeyInterp::Eased,
},
look: Look::default(),
lens: Lens::default(),
shake: None,
}
}
pub fn blend_in(mut self, secs: f32, ease: EaseFunction) -> Self {
self.blend_in = Some(Blend { secs, ease });
self
}
pub fn look(mut self, look: Look) -> Self {
self.look = look;
self
}
pub fn lens(mut self, lens: Lens) -> Self {
self.lens = lens;
self
}
pub fn shake(mut self, shake: Shake) -> Self {
self.shake = Some(shake);
self
}
}
impl SequenceAsset {
pub fn single_shot(name: impl Into<String>, shot: Shot) -> Self {
Self {
name: name.into(),
shots: vec![shot],
markers: Vec::new(),
blend_out: None,
}
}
}