bevy_image_export 0.16.0

Bevy plugin for rendering image sequences
Documentation
mod common;

use crate::common::graceperiod::{GracePeriodPlugin, GracefulFrameCount};
use bevy::{
    camera::RenderTarget,
    prelude::*,
    render::{
        render_resource::{
            Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
        },
        RenderPlugin,
    },
};
use bevy_image_export::{ImageExport, ImageExportPlugin, ImageExportSource};
use std::f32::consts::PI;

const WIDTH: u32 = 768;
const HEIGHT: u32 = 768;

fn main() {
    let export_plugin = ImageExportPlugin::default();
    let export_threads = export_plugin.threads.clone();

    App::new()
        .add_plugins((
            DefaultPlugins
                .set(WindowPlugin {
                    primary_window: Some(Window {
                        resolution: (WIDTH, HEIGHT).into(),
                        ..default()
                    }),
                    ..default()
                })
                .set(RenderPlugin {
                    synchronous_pipeline_compilation: true,
                    ..default()
                }),
            GracePeriodPlugin::default(),
            export_plugin,
        ))
        .insert_resource(GlobalAmbientLight {
            color: Color::WHITE,
            brightness: 1000.0,
            affects_lightmapped_meshes: true,
        })
        .add_systems(Update, (setup, update).chain())
        .run();

    export_threads.finish();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut images: ResMut<Assets<Image>>,
    mut export_sources: ResMut<Assets<ImageExportSource>>,
    frame_count: Res<GracefulFrameCount>,
) {
    if frame_count.frame() != 1 {
        return;
    }

    let output_texture_handle = {
        let size = Extent3d {
            width: WIDTH,
            height: HEIGHT,
            ..default()
        };
        let mut export_texture = Image {
            texture_descriptor: TextureDescriptor {
                label: None,
                size,
                dimension: TextureDimension::D2,
                format: TextureFormat::Rgba8UnormSrgb,
                mip_level_count: 1,
                sample_count: 1,
                usage: TextureUsages::COPY_DST
                    | TextureUsages::COPY_SRC
                    | TextureUsages::RENDER_ATTACHMENT,
                view_formats: &[],
            },
            ..default()
        };
        export_texture.resize(size);

        images.add(export_texture)
    };

    commands.spawn((
        Camera3d::default(),
        Transform::from_translation(4.2 * Vec3::Z),
        children![(
            Camera3d::default(),
            RenderTarget::Image(output_texture_handle.clone().into()),
            Camera::default(),
        )],
    ));

    commands.spawn(ImageExport(export_sources.add(output_texture_handle)));

    commands.spawn((
        Mesh3d(meshes.add(Mesh::from(Cuboid::default()))),
        MeshMaterial3d(materials.add(Color::srgb(1.0, 0.0, 0.0))),
        Moving,
    ));
}

#[derive(Component)]
struct Moving;

fn update(
    mut transforms: Query<&mut Transform, With<Moving>>,
    frame_count: Res<GracefulFrameCount>,
) {
    let frame = frame_count.frame().wrapping_sub(1);
    let theta = frame as f32 * 0.25 * PI;
    for mut transform in &mut transforms {
        transform.translation = Vec3::new(theta.sin(), theta.cos(), 0.0);
    }
}