mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-28 08:58:07 +00:00
playback in headless mode
This commit is contained in:
@@ -41,3 +41,5 @@ Thumbs.db
|
|||||||
/target
|
/target
|
||||||
|
|
||||||
/screen_shots
|
/screen_shots
|
||||||
|
|
||||||
|
/frames
|
||||||
@@ -17,7 +17,7 @@ use itertools::Itertools;
|
|||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use space_editor::{prelude::PrefabMarker, space_prefab::editor_registry::EditorRegistryExt};
|
use space_editor::{prelude::PrefabMarker, space_prefab::editor_registry::EditorRegistryExt};
|
||||||
|
|
||||||
use crate::interaction::{MarkedForDeletion, Selectable};
|
use crate::{headless::HeadlessSettings, interaction::{MarkedForDeletion, Selectable}, StudioMode};
|
||||||
|
|
||||||
pub struct TimelinePlugin;
|
pub struct TimelinePlugin;
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ impl Default for TransformKeyframe {
|
|||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Keyframe {
|
pub struct Keyframe {
|
||||||
target: AnimationTarget,
|
target: AnimationTarget,
|
||||||
time: f32
|
pub time: f32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Clone, Debug, Default, Reflect, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Component, Clone, Debug, Default, Reflect, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -262,9 +262,12 @@ fn process_timeline_events(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_tick(mut r_global_seek_time: ResMut<GlobalSeekTime>, time: Res<Time>) {
|
fn update_tick(mut r_global_seek_time: ResMut<GlobalSeekTime>, time: Res<Time>, headless_settings: Option<Res<HeadlessSettings>>) {
|
||||||
if r_global_seek_time.playing {
|
if r_global_seek_time.playing {
|
||||||
let delta = time.delta_seconds();
|
let delta = match headless_settings {
|
||||||
|
Some(headless_settings) => headless_settings.frame_delta,
|
||||||
|
None => time.delta_seconds()
|
||||||
|
};
|
||||||
r_global_seek_time.time += delta;
|
r_global_seek_time.time += delta;
|
||||||
r_global_seek_time.delta = Some(delta);
|
r_global_seek_time.delta = Some(delta);
|
||||||
}
|
}
|
||||||
@@ -529,7 +532,6 @@ pub fn timeline_bound_component_animator_system<T: Component>(
|
|||||||
events: ResMut<Events<TweenCompleted>>,
|
events: ResMut<Events<TweenCompleted>>,
|
||||||
) {
|
) {
|
||||||
if time.is_changed() && time.time >= 0.0 {
|
if time.is_changed() && time.time >= 0.0 {
|
||||||
info!("Animating with time: {time:?}");
|
|
||||||
let mut events: Mut<Events<TweenCompleted>> = events.into();
|
let mut events: Mut<Events<TweenCompleted>> = events.into();
|
||||||
for (entity, target, mut animator) in query.iter_mut() {
|
for (entity, target, mut animator) in query.iter_mut() {
|
||||||
if animator.tweenable().duration() <= Duration::from_secs_f32(0.0001) {
|
if animator.tweenable().duration() <= Duration::from_secs_f32(0.0001) {
|
||||||
@@ -537,7 +539,6 @@ pub fn timeline_bound_component_animator_system<T: Component>(
|
|||||||
}
|
}
|
||||||
let mut target = ComponentTarget::new(target);
|
let mut target = ComponentTarget::new(target);
|
||||||
if let Some(delta) = time.delta {
|
if let Some(delta) = time.delta {
|
||||||
info!("Playing animation with delta {delta}");
|
|
||||||
animator.tweenable_mut().tick(
|
animator.tweenable_mut().tick(
|
||||||
Duration::from_secs_f32(delta),
|
Duration::from_secs_f32(delta),
|
||||||
&mut target,
|
&mut target,
|
||||||
|
|||||||
+41
-7
@@ -9,11 +9,7 @@ use space_editor::prelude::{EditorEvent, EditorPrefabPath};
|
|||||||
use wgpu::Extent3d;
|
use wgpu::Extent3d;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bvh::{BvhAnimCompleteEvent, TestBvh},
|
anim::timeline_animation::{Keyframe, TimelineInteractionEvent}, bvh::{BvhAnimCompleteEvent, TestBvh}, scene::SceneState, screenshot::{ScreenCapture, ScreenShotPlugin}, BvhPlugin, CommonPlugins, LoadLibraryObjectEvent, LoadSceneEvent, MainCamera, MixamoImportEvent, MixamoPlugin, RemoteAssetPlugin, SkyboxId, StudioMode, WorldGridPlugin
|
||||||
scene::SceneState,
|
|
||||||
screenshot::{ScreenCapture, ScreenShotPlugin},
|
|
||||||
BvhPlugin, CommonPlugins, LoadLibraryObjectEvent, LoadSceneEvent, MainCamera,
|
|
||||||
MixamoImportEvent, MixamoPlugin, RemoteAssetPlugin, SkyboxId, StudioMode, WorldGridPlugin,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Resource, Clone)]
|
#[derive(Debug, Resource, Clone)]
|
||||||
@@ -24,6 +20,14 @@ pub enum HeadlessMode {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub enum CameraMotion {
|
||||||
|
Orbit(f32),
|
||||||
|
Pan(f32),
|
||||||
|
Zoom(f32),
|
||||||
|
Static,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_headless(
|
pub fn start_headless(
|
||||||
mode: HeadlessMode,
|
mode: HeadlessMode,
|
||||||
fps: u32,
|
fps: u32,
|
||||||
@@ -39,6 +43,8 @@ pub fn start_headless(
|
|||||||
max_frames: frames_to_capture,
|
max_frames: frames_to_capture,
|
||||||
output_dims: image_res,
|
output_dims: image_res,
|
||||||
output_dir: outout_dir.to_string(),
|
output_dir: outout_dir.to_string(),
|
||||||
|
fps: fps as f32,
|
||||||
|
frame_delta: 1.0 / (fps as f32),
|
||||||
});
|
});
|
||||||
|
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
@@ -51,7 +57,7 @@ pub fn start_headless(
|
|||||||
close_when_requested: true,
|
close_when_requested: true,
|
||||||
}),
|
}),
|
||||||
ScreenShotPlugin,
|
ScreenShotPlugin,
|
||||||
CommonPlugins.build().disable::<WorldGridPlugin>(),
|
CommonPlugins.build().disable::<WorldGridPlugin>()
|
||||||
));
|
));
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
@@ -80,7 +86,8 @@ pub fn start_headless(
|
|||||||
cmd.insert_resource(SkyboxId(skybox.clone()));
|
cmd.insert_resource(SkyboxId(skybox.clone()));
|
||||||
ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone())))
|
ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone())))
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.add_systems(OnEnter(SceneState::Active), start_timeline_animation);
|
||||||
}
|
}
|
||||||
HeadlessMode::Mixamo(mixamo_path, skybox) => {
|
HeadlessMode::Mixamo(mixamo_path, skybox) => {
|
||||||
app.add_plugins(MixamoPlugin);
|
app.add_plugins(MixamoPlugin);
|
||||||
@@ -120,6 +127,8 @@ pub struct HeadlessSettings {
|
|||||||
pub output_dims: Extent3d,
|
pub output_dims: Extent3d,
|
||||||
pub output_dir: String,
|
pub output_dir: String,
|
||||||
pub max_frames: u32,
|
pub max_frames: u32,
|
||||||
|
pub fps: f32,
|
||||||
|
pub frame_delta: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HeadlessSettings {
|
impl Default for HeadlessSettings {
|
||||||
@@ -132,6 +141,8 @@ impl Default for HeadlessSettings {
|
|||||||
},
|
},
|
||||||
output_dir: "./screen_shots/".to_string(),
|
output_dir: "./screen_shots/".to_string(),
|
||||||
max_frames: u32::MAX,
|
max_frames: u32::MAX,
|
||||||
|
fps: 24.0,
|
||||||
|
frame_delta: 1.0 / 24.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,3 +183,26 @@ fn check_for_mixamo_capture_complete(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_timeline_animation(
|
||||||
|
mut headless_settings: ResMut<HeadlessSettings>,
|
||||||
|
keyframes: Query<&Keyframe>,
|
||||||
|
camera: Query<Entity, With<MainCamera>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut ev: EventWriter<TimelineInteractionEvent>
|
||||||
|
) {
|
||||||
|
if keyframes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commands.remove_resource::<CameraMotion>();
|
||||||
|
let total_time = keyframes.iter().fold(0f32, |previous, keyframe| previous.max(keyframe.time));
|
||||||
|
let frames = (total_time * headless_settings.fps).ceil() as u32;
|
||||||
|
headless_settings.max_frames = frames;
|
||||||
|
ev.send(TimelineInteractionEvent::Play);
|
||||||
|
let camera = camera.single();
|
||||||
|
commands.entity(camera).insert(
|
||||||
|
ScreenCapture::from_out(&headless_settings.output_dir)
|
||||||
|
//Part of quick hack, see screenshot.rs#261
|
||||||
|
.with_max_frames(headless_settings.max_frames + 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use bevy::{prelude::*, render::render_resource::Extent3d, transform::components::Transform};
|
use bevy::{prelude::*, render::render_resource::Extent3d, transform::components::Transform};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use studio::{
|
use studio::{
|
||||||
headless::{start_headless, HeadlessMode},
|
headless::{start_headless, CameraMotion, HeadlessMode},
|
||||||
scene::SceneState,
|
scene::SceneState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,19 +180,14 @@ fn main() {
|
|||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
enum CameraMotion {
|
|
||||||
Orbit(f32),
|
|
||||||
Pan(f32),
|
|
||||||
Zoom(f32),
|
|
||||||
Static,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn update_camera_motion(
|
pub(crate) fn update_camera_motion(
|
||||||
mut camera: Query<&mut Transform, (With<Camera>, Without<Parent>)>,
|
mut camera: Query<&mut Transform, (With<Camera>, Without<Parent>)>,
|
||||||
camera_motion: Res<CameraMotion>,
|
camera_motion: Option<Res<CameraMotion>>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
|
let Some(camera_motion) = camera_motion else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
println!("{}", camera.iter().count());
|
println!("{}", camera.iter().count());
|
||||||
let Ok(mut main_cam_transform) = camera.get_single_mut() else {
|
let Ok(mut main_cam_transform) = camera.get_single_mut() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+3
-7
@@ -57,7 +57,7 @@ impl Plugin for ScenePlugin {
|
|||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(init_object_loading, init_scene_import, init_scene_loading)
|
(init_object_loading, init_scene_import, init_scene_loading, start_capture.run_if(is_headless))
|
||||||
.in_set(SceneSpawnerSystem),
|
.in_set(SceneSpawnerSystem),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
@@ -65,7 +65,6 @@ impl Plugin for ScenePlugin {
|
|||||||
(
|
(
|
||||||
tag_scene_elements,
|
tag_scene_elements,
|
||||||
frame_scene_in_view,
|
frame_scene_in_view,
|
||||||
start_capture.run_if(is_headless),
|
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(SceneSpawnerSystem),
|
.in_set(SceneSpawnerSystem),
|
||||||
@@ -339,10 +338,7 @@ fn initialiaze_headless(
|
|||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
CameraControllerBundle::new(focus, camera_xform.translation),
|
CameraControllerBundle::new(focus, camera_xform.translation)
|
||||||
ScreenCapture::from_out(&headless_settings.output_dir)
|
|
||||||
//Part of quick hack, see screenshot.rs#261
|
|
||||||
.with_max_frames(headless_settings.max_frames + 3),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +516,7 @@ fn frame_scene_in_view(
|
|||||||
cam_xform.translation = cam.focus + cam_xform.back() * radius;
|
cam_xform.translation = cam.focus + cam_xform.back() * radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_capture(mut captures: Query<&mut ScreenCapture>) {
|
fn start_capture(mut captures: Query<&mut ScreenCapture, Added<ScreenCapture>>) {
|
||||||
for mut capture in captures.iter_mut() {
|
for mut capture in captures.iter_mut() {
|
||||||
capture.start_capturing();
|
capture.start_capturing();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user