mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-27 08:48:14 +00:00
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:
+119
-2
@@ -1,11 +1,20 @@
|
||||
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
|
||||
|
||||
use bevy::{app::PluginGroupBuilder, prelude::*};
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{
|
||||
app::{AppExit, PluginGroupBuilder, ScheduleRunnerPlugin},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_mod_picking::DefaultPickingPlugins;
|
||||
use bevy_web_asset::WebAssetPlugin;
|
||||
use bvh::{BvhAnimCompleteEvent, TestBvh};
|
||||
use screenshot::{ScreenCapture, ScreenShotPlugin};
|
||||
use space_editor::prelude::{EditorEvent, EditorPrefabPath};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wgpu::Extent3d;
|
||||
|
||||
mod anim;
|
||||
mod bvh;
|
||||
@@ -20,6 +29,7 @@ pub mod math;
|
||||
mod mixamo;
|
||||
mod persistance;
|
||||
mod scene;
|
||||
pub mod screenshot;
|
||||
mod skybox;
|
||||
mod ui;
|
||||
pub mod viewport;
|
||||
@@ -53,6 +63,7 @@ pub(crate) struct MainCamera;
|
||||
#[derive(Resource, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum StudioMode {
|
||||
Editor,
|
||||
Headless,
|
||||
Viewer,
|
||||
}
|
||||
|
||||
@@ -62,7 +73,7 @@ pub(crate) fn is_editor(r_studio_mode: Res<StudioMode>) -> bool {
|
||||
|
||||
/// The collection of plugins that should be loaded for both viewer-mode and
|
||||
/// editor-mode instances of the app.
|
||||
struct CommonPlugins;
|
||||
pub struct CommonPlugins;
|
||||
impl PluginGroup for CommonPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
@@ -272,3 +283,109 @@ pub fn start_internal(
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
pub enum HeadlessMode {
|
||||
Bvh(String, String),
|
||||
Scene(String, String),
|
||||
}
|
||||
|
||||
pub fn start_headless(
|
||||
mode: HeadlessMode,
|
||||
fps: u32,
|
||||
outout_dir: &str,
|
||||
frames_to_capture: u32,
|
||||
image_res: Extent3d,
|
||||
) -> App {
|
||||
let mut app = App::new();
|
||||
|
||||
app.insert_resource(StudioMode::Headless);
|
||||
app.insert_resource(HeadlessSettings {
|
||||
frames_to_capture,
|
||||
output_dims: image_res,
|
||||
output_dir: outout_dir.to_string(),
|
||||
});
|
||||
|
||||
app.add_plugins((
|
||||
ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / fps as f64)),
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: None,
|
||||
exit_condition: bevy::window::ExitCondition::DontExit,
|
||||
close_when_requested: true,
|
||||
}),
|
||||
ScreenShotPlugin,
|
||||
CommonPlugins,
|
||||
));
|
||||
|
||||
match mode {
|
||||
HeadlessMode::Bvh(bvh_dir, skybox) => {
|
||||
app.add_plugins(BvhPlugin);
|
||||
app.add_systems(
|
||||
Startup,
|
||||
move |mut cmd: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut ew: EventWriter<LoadSceneEvent>| {
|
||||
cmd.insert_resource(SkyboxId(skybox.clone()));
|
||||
cmd.insert_resource(TestBvh(asset_server.load(bvh_dir.clone())));
|
||||
|
||||
ew.send(LoadSceneEvent("Mannequin.gltf".to_string()));
|
||||
},
|
||||
);
|
||||
|
||||
app.add_systems(PostUpdate, check_for_capture_complete);
|
||||
}
|
||||
HeadlessMode::Scene(scene, skybox) => {
|
||||
app.add_systems(
|
||||
Startup,
|
||||
move |mut cmd: Commands, mut ew: EventWriter<EditorEvent>| {
|
||||
cmd.insert_resource(SkyboxId(skybox.clone()));
|
||||
ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone())))
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
pub fn is_headless(studio_state: Res<StudioMode>) -> bool {
|
||||
*studio_state == StudioMode::Headless
|
||||
}
|
||||
|
||||
pub fn is_not_headless(studio_state: Res<StudioMode>) -> bool {
|
||||
*studio_state != StudioMode::Headless
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct HeadlessSettings {
|
||||
pub output_dims: Extent3d,
|
||||
pub output_dir: String,
|
||||
pub frames_to_capture: u32,
|
||||
}
|
||||
|
||||
impl Default for HeadlessSettings {
|
||||
fn default() -> Self {
|
||||
HeadlessSettings {
|
||||
output_dims: Extent3d {
|
||||
width: 1280,
|
||||
height: 720,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
output_dir: "./screen_shots/".to_string(),
|
||||
frames_to_capture: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_for_capture_complete(
|
||||
main_camera: Query<&ScreenCapture, With<MainCamera>>,
|
||||
mut close_app: EventWriter<AppExit>,
|
||||
bvh_event_reader: EventReader<BvhAnimCompleteEvent>,
|
||||
) {
|
||||
let Ok(screen_capture) = main_camera.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if screen_capture.is_complete() || !bvh_event_reader.is_empty() {
|
||||
close_app.send(AppExit);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user