#![allow(dead_code)]
pub mod body;
use bevy::{
app::SubApps,
asset::RenderAssetUsages,
camera::RenderTarget,
prelude::*,
render::{
RenderPlugin,
render_resource::{Extent3d, PollType, TextureDimension, TextureFormat, TextureUsages},
renderer::RenderDevice,
view::screenshot::{Screenshot, save_to_disk},
},
window::ExitCondition,
winit::WinitPlugin,
};
pub struct Recorder {
app: SubApps,
target: Handle<Image>,
out: String,
frame: u32,
}
impl Recorder {
pub fn new(width: u32, height: u32, camera: Transform, out: &str) -> Option<Recorder> {
Recorder::new_with(width, height, camera, out, |_| {})
}
pub fn new_with(
width: u32,
height: u32,
camera: Transform,
out: &str,
add_plugins: impl FnOnce(&mut App),
) -> Option<Recorder> {
if let Err(e) = std::fs::create_dir_all(out) {
error!("capture: cannot create {out}: {e}");
return None;
}
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: None,
exit_condition: ExitCondition::DontExit,
..default()
})
.set(RenderPlugin { synchronous_pipeline_compilation: true, ..default() })
.disable::<WinitPlugin>(),
);
add_plugins(&mut app);
app.finish();
app.cleanup();
let mut app = std::mem::take(app.sub_apps_mut());
let mut image = Image::new_uninit(
Extent3d { width, height, depth_or_array_layers: 1 },
TextureDimension::D2,
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
);
image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
let target = app.main.world_mut().resource_mut::<Assets<Image>>().add(image);
app.main.world_mut().spawn((Camera3d::default(), RenderTarget::from(target.clone()), camera));
Some(Recorder { app, target, out: out.to_string(), frame: 0 })
}
pub fn world(&mut self) -> &mut World {
self.app.main.world_mut()
}
pub fn app(&mut self) -> &mut SubApps {
&mut self.app
}
pub fn shoot(&mut self) {
let path = format!("{}/frame{:04}.png", self.out, self.frame);
self.app.main.world_mut().spawn(Screenshot::image(self.target.clone())).observe(save_to_disk(path));
self.frame += 1;
self.step();
}
pub fn warm_up(&mut self, frames: u32) {
for _ in 0..frames {
self.step();
}
}
pub fn step(&mut self) {
self.app.update();
let device = self.app.main.world().resource::<RenderDevice>().wgpu_device().clone();
if let Err(e) = device.poll(PollType::Wait { submission_index: None, timeout: None }) {
warn!("capture: device poll failed, frame may be torn: {e:?}");
}
}
pub fn finish(mut self) -> u32 {
for _ in 0..4 {
self.step();
}
self.frame
}
}
pub fn arg(flag: &str) -> Option<String> {
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
if a == flag {
match args.next() {
Some(v) => return Some(v),
None => {
warn!("capture: {flag} given with no value; ignoring it");
return None;
}
}
}
}
None
}
pub fn light_and_floor(world: &mut World) {
world.insert_resource(GlobalAmbientLight {
color: Color::srgb(0.62, 0.66, 0.78),
brightness: 900.0,
..default()
});
world.spawn((
DirectionalLight { illuminance: 9_000.0, shadow_maps_enabled: true, ..default() },
Transform::from_xyz(4.0, 8.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let floor = world
.resource_mut::<Assets<Mesh>>()
.add(Mesh::from(Plane3d::default().mesh().size(14.0, 14.0)));
let dark = world.resource_mut::<Assets<StandardMaterial>>().add(StandardMaterial {
base_color: Color::srgb(0.16, 0.16, 0.18),
perceptual_roughness: 0.95,
..default()
});
world.spawn((Mesh3d(floor), MeshMaterial3d(dark)));
}
pub fn material(world: &mut World, color: Color, roughness: f32) -> Handle<StandardMaterial> {
world.resource_mut::<Assets<StandardMaterial>>().add(StandardMaterial {
base_color: color,
perceptual_roughness: roughness,
..default()
})
}