Files
effectcraft/crates/project/src/layer.rs
T

371 lines
11 KiB
Rust

use super::item::{ItemId, LayerId};
use super::settings::Marker;
use super::*;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum LightKind {
Parallel,
Spot,
#[default]
Point,
Ambient,
Environment,
}
impl LightKind {
pub const ALL: [LightKind; 5] = [LightKind::Parallel, LightKind::Spot, LightKind::Point, LightKind::Ambient, LightKind::Environment];
pub fn label(self) -> &'static str {
match self {
LightKind::Parallel => "Parallel",
LightKind::Spot => "Spot",
LightKind::Point => "Point",
LightKind::Ambient => "Ambient",
LightKind::Environment => "Environment",
}
}
}
/// A parametric 3D primitive (Layer ▸ New ▸ Cube/Sphere/Plane/Torus/Cone/Cylinder).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PrimitiveKind {
#[default]
Cube,
Sphere,
Plane,
Torus,
Cone,
Cylinder,
}
impl PrimitiveKind {
pub const ALL: [PrimitiveKind; 6] =
[PrimitiveKind::Cube, PrimitiveKind::Sphere, PrimitiveKind::Plane, PrimitiveKind::Torus, PrimitiveKind::Cone, PrimitiveKind::Cylinder];
pub fn label(self) -> &'static str {
match self {
PrimitiveKind::Cube => "Cube",
PrimitiveKind::Sphere => "Sphere",
PrimitiveKind::Plane => "Plane",
PrimitiveKind::Torus => "Torus",
PrimitiveKind::Cone => "Cone",
PrimitiveKind::Cylinder => "Cylinder",
}
}
pub fn parse(s: &str) -> Option<PrimitiveKind> {
PrimitiveKind::ALL.into_iter().find(|k| k.label().eq_ignore_ascii_case(s.trim()))
}
}
/// What a layer shows.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum LayerSource {
/// Footage item (video, still, sequence, audio).
Footage {
item: ItemId,
},
/// Nested composition (precomp).
Comp {
item: ItemId,
},
/// Solid item (solids also back adjustment layers).
Solid {
item: ItemId,
},
Text,
Shape,
Null,
Camera,
Light {
kind: LightKind,
},
/// An imported 3D model (a footage item of kind [`FootageKind::Model`]; Advanced 3D).
Model {
item: ItemId,
},
/// A parametric 3D primitive (Advanced 3D).
Primitive {
kind: PrimitiveKind,
},
}
impl LayerSource {
pub fn item(&self) -> Option<ItemId> {
match self {
LayerSource::Footage { item } | LayerSource::Comp { item } | LayerSource::Solid { item } | LayerSource::Model { item } => Some(*item),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
LayerSource::Footage { .. } => "Footage",
LayerSource::Comp { .. } => "Composition",
LayerSource::Solid { .. } => "Solid",
LayerSource::Text => "Text",
LayerSource::Shape => "Shape",
LayerSource::Null => "Null",
LayerSource::Camera => "Camera",
LayerSource::Light { .. } => "Light",
LayerSource::Model { .. } => "3D Model",
LayerSource::Primitive { .. } => "3D Primitive",
}
}
/// Layers with pixels (cameras, lights and nulls have none).
pub fn is_av(&self) -> bool {
!matches!(self, LayerSource::Camera | LayerSource::Light { .. } | LayerSource::Null)
}
/// A 3D model or primitive layer (meshes; drawn by the Advanced 3D renderer).
pub fn is_model(&self) -> bool {
matches!(self, LayerSource::Model { .. } | LayerSource::Primitive { .. })
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Quality {
#[default]
Best,
Draft,
Wireframe,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Sampling {
#[default]
Bilinear,
Bicubic,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum FrameBlend {
#[default]
Off,
FrameMix,
PixelMotion,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Switches {
pub video: bool,
pub audio: bool,
pub solo: bool,
pub locked: bool,
pub shy: bool,
/// Collapse transformations (precomps) / continuously rasterize (vector layers).
pub collapse: bool,
pub quality: Quality,
pub sampling: Sampling,
pub effects: bool,
pub frame_blend: FrameBlend,
pub motion_blur: bool,
pub adjustment: bool,
pub three_d: bool,
pub guide: bool,
}
impl Default for Switches {
fn default() -> Self {
Switches {
video: true,
audio: true,
solo: false,
locked: false,
shy: false,
collapse: false,
quality: Quality::Best,
sampling: Sampling::Bilinear,
effects: true,
frame_blend: FrameBlend::Off,
motion_blur: false,
adjustment: false,
three_d: false,
guide: false,
}
}
}
// What a frame's pixels depend on. Caches key on it: the Audio, Lock and Shy switches and Hide
// Shy Layers don't change pixels, so toggling them keeps cached frames (#103).
impl Switches {
/// These switches with Audio, Lock and Shy at their defaults.
pub fn pixels(&self) -> Switches {
Switches { audio: true, locked: false, shy: false, ..self.clone() }
}
}
impl Layer {
/// Draws the same pixels as `o`: equal but for the Audio, Lock and Shy switches.
pub fn same_pixels(&self, o: &Layer) -> bool {
// Every field (no `..`: a new field must be considered here); the property tree last.
let Layer {
id,
name,
source,
label,
comment,
start_time,
in_point,
out_point,
stretch,
switches,
blend_mode,
preserve_transparency,
track_matte,
parent,
markers,
markers_locked,
auto_orient,
environment,
environment_background,
props,
} = self;
*id == o.id
&& *name == o.name
&& *source == o.source
&& *label == o.label
&& *comment == o.comment
&& *start_time == o.start_time
&& *in_point == o.in_point
&& *out_point == o.out_point
&& *stretch == o.stretch
&& switches.pixels() == o.switches.pixels()
&& *blend_mode == o.blend_mode
&& *preserve_transparency == o.preserve_transparency
&& *track_matte == o.track_matte
&& *parent == o.parent
&& *markers == o.markers
&& *markers_locked == o.markers_locked
&& *auto_orient == o.auto_orient
&& *environment == o.environment
&& *environment_background == o.environment_background
&& *props == o.props
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum MatteKind {
#[default]
Alpha,
AlphaInverted,
Luma,
LumaInverted,
}
impl MatteKind {
pub const ALL: [MatteKind; 4] = [MatteKind::Alpha, MatteKind::AlphaInverted, MatteKind::Luma, MatteKind::LumaInverted];
pub fn label(self) -> &'static str {
match self {
MatteKind::Alpha => "Alpha Matte",
MatteKind::AlphaInverted => "Alpha Inverted Matte",
MatteKind::Luma => "Luma Matte",
MatteKind::LumaInverted => "Luma Inverted Matte",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrackMatte {
pub layer: LayerId,
pub kind: MatteKind,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AutoOrient {
#[default]
Off,
AlongPath,
TowardsCamera,
TowardsPointOfInterest,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Layer {
pub id: LayerId,
pub name: String,
pub source: LayerSource,
pub label: Label,
#[serde(default)]
pub comment: String,
/// Comp time where the layer's time 0 sits.
pub start_time: Tick,
/// Visible span in comp time.
pub in_point: Tick,
pub out_point: Tick,
/// Time stretch in percent (100 = normal, negative = reversed).
pub stretch: f64,
pub switches: Switches,
pub blend_mode: BlendMode,
#[serde(default)]
pub preserve_transparency: bool,
#[serde(default)]
pub track_matte: Option<TrackMatte>,
#[serde(default)]
pub parent: Option<LayerId>,
#[serde(default)]
pub markers: Vec<Marker>,
/// Layer ▸ Markers ▸ Lock Markers.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub markers_locked: bool,
#[serde(default)]
pub auto_orient: AutoOrient,
/// Layer ▸ Environment Layer: the layer is the environment (image-based light and
/// reflections) of Advanced 3D comps instead of being drawn.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub environment: bool,
/// Layer ▸ Light ▸ Create Environment Light Background Layer: the layer's (equirectangular)
/// image is drawn as the 3D scene's backdrop, seen through the camera, instead of as a card.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub environment_background: bool,
/// The property tree (Masks, Effects, Transform, Text, Contents, Camera/Light options…).
pub props: PropGroup,
}
impl Layer {
pub fn is_active_at(&self, t: Tick) -> bool {
t >= self.in_point && t < self.out_point
}
/// Layer time for a comp time (stretch-aware).
pub fn layer_time(&self, comp_t: Tick) -> Tick {
let d = comp_t - self.start_time;
if (self.stretch - 100.0).abs() < 1e-9 { d } else { Tick((d.0 as f64 * 100.0 / self.stretch) as i64) }
}
/// Comp time for a layer time.
pub fn comp_time(&self, layer_t: Tick) -> Tick {
if (self.stretch - 100.0).abs() < 1e-9 { self.start_time + layer_t } else { self.start_time + Tick((layer_t.0 as f64 * self.stretch / 100.0) as i64) }
}
pub fn transform(&self) -> Option<&PropGroup> {
self.props.sub("transform")
}
pub fn transform_mut(&mut self) -> Option<&mut PropGroup> {
self.props.sub_mut("transform")
}
pub fn effects(&self) -> Option<&PropGroup> {
self.props.sub("effects")
}
pub fn masks(&self) -> Option<&PropGroup> {
self.props.sub("masks")
}
pub fn is_camera(&self) -> bool {
matches!(self.source, LayerSource::Camera)
}
pub fn is_light(&self) -> bool {
matches!(self.source, LayerSource::Light { .. })
}
/// The layer participates in 3D (3D switch, or cameras/lights which are always 3D).
pub fn is_3d(&self) -> bool {
self.switches.three_d || self.is_camera() || self.is_light()
}
pub fn has_video(&self) -> bool {
self.source.is_av() && self.switches.video
}
}