motionloom 0.1.0

MotionLoom DSL parser and renderer for video effects, scene graphs, motion graphics, and world graphs.
Documentation
use crate::dsl::GraphScript;
use crate::scene::model::SceneNode;

pub(crate) fn scene_nodes_for_present(graph: &GraphScript) -> Option<&[SceneNode]> {
    let present = graph.present.from.as_str();
    if present == "scene" {
        if !graph.scene_nodes.is_empty() {
            return Some(&graph.scene_nodes);
        }
        return graph.scenes.first().map(|scene| scene.children.as_slice());
    }
    let scene_id = present.strip_prefix("scene:").unwrap_or(present);
    graph
        .scenes
        .iter()
        .find(|scene| scene.id == scene_id)
        .map(|scene| scene.children.as_slice())
}

pub(crate) fn scene_nodes_contain_image_or_svg(nodes: &[SceneNode]) -> bool {
    nodes.iter().any(|node| match node {
        SceneNode::Image(_) | SceneNode::Svg(_) => true,
        SceneNode::Timeline(timeline) => scene_nodes_contain_image_or_svg(&timeline.children),
        SceneNode::Track(track) => scene_nodes_contain_image_or_svg(&track.children),
        SceneNode::Sequence(sequence) => scene_nodes_contain_image_or_svg(&sequence.children),
        SceneNode::Chain(chain) => scene_nodes_contain_image_or_svg(&chain.children),
        SceneNode::Group(group) => scene_nodes_contain_image_or_svg(&group.children),
        SceneNode::Part(part) => scene_nodes_contain_image_or_svg(&part.children),
        SceneNode::Repeat(repeat) => scene_nodes_contain_image_or_svg(&repeat.children),
        SceneNode::Camera(camera) => scene_nodes_contain_image_or_svg(&camera.children),
        SceneNode::Character(character) => scene_nodes_contain_image_or_svg(&character.children),
        SceneNode::Mask(mask) => scene_nodes_contain_image_or_svg(&mask.children),
        SceneNode::Precompose(precompose) => scene_nodes_contain_image_or_svg(&precompose.children),
        SceneNode::Layer(layer) => scene_nodes_contain_image_or_svg(&layer.children),
        _ => false,
    })
}

pub(crate) fn scene_nodes_require_cpu_scene_compositing(nodes: &[SceneNode]) -> bool {
    nodes.iter().any(|node| match node {
        SceneNode::Precompose(_) => true,
        SceneNode::Layer(layer) => {
            layer.is_3d
                || layer.source.is_some()
                || layer.mask.is_some()
                || layer.matte.is_some()
                || layer.effect.is_some()
                || !is_default_scene_number(&layer.opacity)
                || !is_normal_blend_name(&layer.blend)
                || scene_nodes_require_cpu_scene_compositing(&layer.children)
        }
        SceneNode::Timeline(timeline) => {
            scene_nodes_require_cpu_scene_compositing(&timeline.children)
        }
        SceneNode::Track(track) => scene_nodes_require_cpu_scene_compositing(&track.children),
        SceneNode::Sequence(sequence) => {
            scene_nodes_require_cpu_scene_compositing(&sequence.children)
        }
        SceneNode::Chain(chain) => scene_nodes_require_cpu_scene_compositing(&chain.children),
        SceneNode::Group(group) => {
            group.mask.is_some() || scene_nodes_require_cpu_scene_compositing(&group.children)
        }
        SceneNode::Mask(mask) => {
            mask.feather.trim() != "0" || scene_nodes_require_cpu_scene_compositing(&mask.children)
        }
        SceneNode::Part(part) => scene_nodes_require_cpu_scene_compositing(&part.children),
        SceneNode::Repeat(repeat) => scene_nodes_require_cpu_scene_compositing(&repeat.children),
        SceneNode::Camera(camera) => scene_nodes_require_cpu_scene_compositing(&camera.children),
        SceneNode::Character(character) => {
            scene_nodes_require_cpu_scene_compositing(&character.children)
        }
        _ => false,
    })
}

fn is_default_scene_number(value: &str) -> bool {
    matches!(value.trim(), "1" | "1.0" | "1.00" | "1.000")
}

fn is_normal_blend_name(value: &str) -> bool {
    let value = value.trim();
    value.is_empty() || value.eq_ignore_ascii_case("normal")
}

pub(crate) fn graph_has_rich_scene_tree(graph: &GraphScript) -> bool {
    !graph.scenes.is_empty() || graph.scene_nodes.iter().any(scene_node_is_rich)
}

fn scene_node_is_rich(node: &SceneNode) -> bool {
    match node {
        SceneNode::Defs(_)
        | SceneNode::Palette(_)
        | SceneNode::Text(_)
        | SceneNode::Image(_)
        | SceneNode::Svg(_) => false,
        SceneNode::Rect(_)
        | SceneNode::PixelGrid(_)
        | SceneNode::Circle(_)
        | SceneNode::Line(_)
        | SceneNode::Polyline(_)
        | SceneNode::Path(_)
        | SceneNode::FaceJaw(_)
        | SceneNode::Shadow(_)
        | SceneNode::Mask(_)
        | SceneNode::Use(_)
        | SceneNode::Layer(_) => true,
        SceneNode::Precompose(precompose) => precompose.children.iter().any(scene_node_is_rich),
        SceneNode::Timeline(timeline) => timeline.children.iter().any(scene_node_is_rich),
        SceneNode::Track(track) => track.children.iter().any(scene_node_is_rich),
        SceneNode::Sequence(sequence) => sequence.children.iter().any(scene_node_is_rich),
        SceneNode::Chain(chain) => chain.children.iter().any(scene_node_is_rich),
        SceneNode::Group(group) => group.children.iter().any(scene_node_is_rich),
        SceneNode::Part(part) => part.children.iter().any(scene_node_is_rich),
        SceneNode::Repeat(repeat) => repeat.children.iter().any(scene_node_is_rich),
        SceneNode::Camera(_) | SceneNode::Character(_) => true,
    }
}