#![allow(clippy::type_complexity, clippy::too_many_arguments)] use bevy::{app::PluginGroupBuilder, prelude::*}; use bevy_mod_picking::DefaultPickingPlugins; use space_editor::prelude::space_undo::UndoPlugin; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; mod camera_controller; mod debug; mod design_system; mod input; mod inspector; mod interaction; mod math; mod scene; mod skybox; mod ui; mod viewport; mod world_grid; use crate::{ camera_controller::CameraControllerPlugin, debug::DebugPlugin, input::StudioInputPlugin, inspector::InspectorPlugin, interaction::InteractionPlugin, math::InterpolationPlugin, scene::{LoadSceneEvent, ScenePlugin}, skybox::{SkyboxId, SkyboxPlugin}, ui::UiLayerPlugin, viewport::ViewportSize, world_grid::WorldGridPlugin, }; #[cfg(feature = "wasm")] mod wasm; #[derive(Component)] pub(crate) struct MainCamera; #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Resource, Clone, Copy, PartialEq, Eq, Debug)] pub enum StudioMode { Editor, Viewer, } pub(crate) fn is_editor(r_studio_mode: Res) -> bool { matches!(*r_studio_mode, StudioMode::Editor) } /// The collection of plugins that should be loaded for both viewer-mode and /// editor-mode instances of the app. struct CommonPlugins; impl PluginGroup for CommonPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() .add(CameraControllerPlugin) .add(DebugPlugin) .add(InspectorPlugin) .add(InterpolationPlugin) .add(ScenePlugin) .add(SkyboxPlugin) .add(StudioInputPlugin) .add(UiLayerPlugin) .add(WorldGridPlugin) } } /// The collection of plugins that should be loaded only for editor-mode /// instances of the app. struct EditorPlugins; impl PluginGroup for EditorPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() .add(InteractionPlugin) .add(UndoPlugin) } } #[cfg_attr(feature = "wasm", wasm_bindgen)] pub fn init(viewport_size: ViewportSize) { #[cfg(feature = "wasm")] console_error_panic_hook::set_once(); let mut app = App::new(); #[cfg(feature = "wasm")] app.add_plugins(viewport::wasm::ViewportPlugin); app.add_plugins(( DefaultPlugins // TODO: To generate asset .meta files (to prevent 404 errors on the // web), it's currently necessary to uncomment the following // block and run the app with this command: // > cargo run -p studio --features bevy/asset_processor,bevy/tonemapping_luts // .set(AssetPlugin { // mode: AssetMode::Processed, // ..default() // }) .set(WindowPlugin { primary_window: Some(Window { resolution: viewport_size.into(), title: "Storyteller Studio".into(), canvas: Some("#studio_canvas".into()), ..default() }), ..default() }), DefaultPickingPlugins, DebugPlugin, InteractionPlugin, InspectorPlugin, StudioInputPlugin, InterpolationPlugin, CameraControllerPlugin, ScenePlugin, SkyboxPlugin, UiLayerPlugin, WorldGridPlugin, UndoPlugin, )) .add_systems( Startup, move |mut cmd: Commands, mut ew: EventWriter| { cmd.insert_resource(SkyboxId("gum_trees_4k".to_string())); ew.send(LoadSceneEvent("bookcase".to_string())); }, ); // Only for registering components for undo use space_editor::prelude::space_undo::AppAutoUndo; app.auto_reflected_undo::(); #[cfg(feature = "wasm")] wasm::initialize(); app.run(); } #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startViewer))] /// Start the application as a read-only viewer for a single library object. /// /// @param object_id ID of the library object to load. /// @param skybox_id ID of the Skybox to use /// @param canvas_selector A CSS selector for the canvas that will host the Bevy /// application. This should be unique (i.e. `querySelectorAll` should not /// return more than one element), so using an ID selector is a good idea. /// @param canvas_alt `alt` text for the HTML canvas element. /// @param viewport_size The size of the canvas in CSS `px` units. pub fn start_viewer( object_id: &str, skybox_id: &str, canvas_selector: &str, canvas_alt: &str, viewport_size: ViewportSize, ) { info!("start_viewer"); start_internal( object_id, skybox_id, canvas_selector, canvas_alt, viewport_size, StudioMode::Viewer, ); } #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startEditor))] pub fn start_editor( object_id: &str, skybox_id: &str, canvas_selector: &str, canvas_alt: &str, viewport_size: ViewportSize, ) { info!("start_editor"); start_internal( object_id, skybox_id, canvas_selector, canvas_alt, viewport_size, StudioMode::Editor, ); } // TODO: These `start_` functiions should be refactored to better support the // needs of the "reaodnly viewer" vs "full studio editor" use cases. #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = start))] pub fn start_internal( object_id: &str, skybox_id: &str, canvas_selector: &str, canvas_alt: &str, viewport_size: ViewportSize, studio_mode: StudioMode, ) { #[cfg(feature = "wasm")] console_error_panic_hook::set_once(); let object_id = object_id.to_string(); let skybox_id = skybox_id.to_string(); let mut app = App::new(); app.insert_resource(studio_mode); app.add_plugins(( #[cfg(feature = "wasm")] viewport::wasm::ViewportPlugin, DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { resolution: viewport_size.into(), title: canvas_alt.into(), canvas: Some(canvas_selector.into()), ..default() }), ..default() }), CommonPlugins, )); if matches!(studio_mode, StudioMode::Editor) { app.add_plugins((DefaultPickingPlugins, EditorPlugins)); // Only for registering components for undo use space_editor::prelude::space_undo::AppAutoUndo; app.auto_reflected_undo::(); } app.add_systems( Startup, move |mut cmd: Commands, mut ew: EventWriter| { cmd.insert_resource(SkyboxId(skybox_id.clone())); ew.send(LoadSceneEvent(object_id.clone())); }, ); #[cfg(feature = "wasm")] wasm::initialize(); app.run(); }