Studio Headless Mode (#36)

This PR adds the capaibility to run Stroyteller studio from the command
line, rendering the result to images. This is the basic functionality,
and there are somethings that I am saving for later when we make this a
more fully fledged feature.

Limitations:
- Right now, you cannot do image output resolutions that aren't 2^n.
This is a bug I plan on tackling later.

If you have questions on how the command line works, use the --help
argument.

Let me know what you think!

---------

Co-authored-by: Danny McGee <dannymcgee@gmail.com>
This commit is contained in:
Brooks Palin
2024-02-19 16:00:30 -07:00
committed by GitHub
parent a1779fb1ec
commit f1c1b85a98
11 changed files with 1016 additions and 28 deletions
+61 -3
View File
@@ -9,19 +9,22 @@ use bevy::{
math::vec2,
pbr::ShadowFilteringMethod,
prelude::*,
render::primitives::Aabb,
render::{camera::RenderTarget, primitives::Aabb},
scene::InstanceId,
};
use space_editor::{prelude::PrefabMarker, space_prefab::component::SceneAutoChild};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
use wgpu::{TextureDescriptor, TextureUsages};
use crate::{
camera_controller::{CameraController, CameraControllerBundle},
debug::DebugPoint,
is_headless, is_not_headless,
math::{Combinable, Extrema},
MainCamera,
screenshot::ScreenCapture,
HeadlessSettings, MainCamera,
};
pub struct ScenePlugin;
@@ -35,7 +38,13 @@ impl Plugin for ScenePlugin {
.insert_resource(Msaa::Off)
.add_state::<SceneState>()
.add_event::<LoadSceneEvent>()
.add_systems(Startup, initialize)
.add_systems(
Startup,
(
initialize.run_if(is_not_headless),
initialiaze_headless.run_if(is_headless),
),
)
.add_systems(Update, init_scene_loading.in_set(SceneSpawnerSystem))
.add_systems(
OnEnter(SceneState::Active),
@@ -210,6 +219,55 @@ fn initialize(mut cmd: Commands) {
));
}
fn initialiaze_headless(
mut cmd: Commands,
mut image_assets: ResMut<Assets<Image>>,
headless_settings: Res<HeadlessSettings>,
) {
// Camera, graphics settings
let focus = Vec3::ZERO;
let camera_xform = Transform::from_xyz(-2.5, 4.5, 5.0).looking_at(focus, Vec3::Y);
let mut test_image = Image {
texture_descriptor: TextureDescriptor {
label: Some("render_texture"),
size: headless_settings.output_dims,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_SRC
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
},
..Default::default()
};
test_image.resize(headless_settings.output_dims);
cmd.spawn((
Name::new("Main Camera"),
MainCamera,
Camera3dBundle {
transform: camera_xform,
tonemapping: Tonemapping::BlenderFilmic,
camera: Camera {
hdr: false,
target: RenderTarget::Image(image_assets.add(test_image)),
..Default::default()
},
..default()
},
CameraControllerBundle::new(focus, camera_xform.translation),
ScreenCapture::number_of_frames(
headless_settings.frames_to_capture,
&headless_settings.output_dir,
),
));
}
fn init_scene_loading(mut cmd: Commands, mut er: EventReader<LoadSceneEvent>) {
if let Some(id) = er.read().last() {
info!("Loading scene: {}", id.0);