mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-22 08:06:01 +00:00
Merge branch 'main' into update-version
This commit is contained in:
+7
-1
@@ -24,6 +24,7 @@ mod hierarchy;
|
||||
mod input;
|
||||
mod inspector;
|
||||
mod interaction;
|
||||
pub mod material;
|
||||
pub mod math;
|
||||
mod mixamo;
|
||||
mod path;
|
||||
@@ -36,6 +37,7 @@ mod ui;
|
||||
pub mod viewport;
|
||||
mod world_grid;
|
||||
|
||||
use crate::material::MaterialPlugin;
|
||||
pub use crate::{
|
||||
anim::AnimPlugin,
|
||||
bvh::BvhPlugin,
|
||||
@@ -89,6 +91,7 @@ impl PluginGroup for CommonPlugins {
|
||||
.add(InspectorPlugin)
|
||||
.add(InterpolationPlugin)
|
||||
.add(ScenePlugin)
|
||||
.add(MaterialPlugin)
|
||||
// .add(SkyboxPlugin)
|
||||
.add(StudioInputPlugin)
|
||||
.add(UiLayerPlugin)
|
||||
@@ -231,7 +234,10 @@ fn init_from_asset(app: &mut App, asset: InitAsset, skybox_id: String) {
|
||||
}
|
||||
InitAsset::ImportedScene(scene) => {
|
||||
app.add_systems(Startup, move |mut ew: EventWriter<ImportSceneEvent>| {
|
||||
ew.send(ImportSceneEvent(scene.clone()));
|
||||
ew.send(ImportSceneEvent {
|
||||
path: scene.clone(),
|
||||
name: None,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::material::short_material::ShortMaterial;
|
||||
use bevy::app::{App, Plugin, PostUpdate};
|
||||
use bevy::asset::{Assets, Handle};
|
||||
use bevy::pbr::StandardMaterial;
|
||||
use bevy::prelude::{Commands, Entity, Query, ResMut};
|
||||
use space_editor::prelude::EditorRegistryExt;
|
||||
|
||||
pub mod short_material;
|
||||
|
||||
pub struct MaterialPlugin;
|
||||
impl Plugin for MaterialPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<ShortMaterial>()
|
||||
.editor_registry::<ShortMaterial>();
|
||||
|
||||
app.add_systems(PostUpdate, apply_short_materials);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_short_materials(
|
||||
mut commands: Commands,
|
||||
q_mats: Query<(Entity, &Handle<StandardMaterial>, &ShortMaterial)>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
for (entity, handle, short) in q_mats.iter() {
|
||||
let Some(material) = materials.get_mut(handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
material.base_color = short.base_color;
|
||||
material.perceptual_roughness = short.perceptual_roughness;
|
||||
material.metallic = short.metallic;
|
||||
material.reflectance = short.reflectance;
|
||||
|
||||
commands.entity(entity).remove::<ShortMaterial>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use bevy::pbr::StandardMaterial;
|
||||
use bevy::prelude::{Color, Component, Reflect};
|
||||
|
||||
use bevy::ecs::reflect::ReflectComponent;
|
||||
use bevy::reflect::std_traits::ReflectDefault;
|
||||
|
||||
#[derive(Reflect, Default, Clone, Component)]
|
||||
#[reflect(Default, Component)]
|
||||
pub struct ShortMaterial {
|
||||
pub base_color: Color,
|
||||
pub perceptual_roughness: f32,
|
||||
pub metallic: f32,
|
||||
pub reflectance: f32,
|
||||
}
|
||||
|
||||
impl ShortMaterial {
|
||||
pub fn new(material: &StandardMaterial) -> Self {
|
||||
Self {
|
||||
base_color: material.base_color,
|
||||
perceptual_roughness: material.perceptual_roughness,
|
||||
metallic: material.metallic,
|
||||
reflectance: material.reflectance,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ use crate::{
|
||||
MainCamera,
|
||||
};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
mod wasm;
|
||||
|
||||
pub struct ScenePlugin;
|
||||
|
||||
#[derive(SystemSet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
@@ -113,7 +116,7 @@ impl Plugin for ScenePlugin {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
wasm::dispatch_object_load_request,
|
||||
wasm::handle_spawn_asset_messages,
|
||||
wasm::notify_state_changes
|
||||
.map(bevy::utils::error)
|
||||
.run_if(resource_changed::<State<SceneState>>),
|
||||
@@ -276,8 +279,11 @@ pub struct LoadSceneEvent(pub String);
|
||||
|
||||
/// Request the application to import and process a glTF scene from an arbitrary
|
||||
/// asset source (e.g., an http path or a Storyteller.ai media token).
|
||||
#[derive(Event, Deref)]
|
||||
pub struct ImportSceneEvent(pub String);
|
||||
#[derive(Event)]
|
||||
pub struct ImportSceneEvent {
|
||||
pub path: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
/// A tag that identifies its entity as a user-facing scene element like a prop,
|
||||
/// a light, or a camera.
|
||||
@@ -409,16 +415,23 @@ fn init_scene_import(
|
||||
r_assets: Res<AssetServer>,
|
||||
mut er: EventReader<ImportSceneEvent>,
|
||||
) {
|
||||
if let Some(path) = er.read().last() {
|
||||
info!("Importing asset: {}", path.0);
|
||||
for event in er.read() {
|
||||
info!("Importing asset: {}", event.path);
|
||||
|
||||
let name = event
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| Name::new(name.clone()))
|
||||
.unwrap_or_else(|| Name::new("Scene"));
|
||||
|
||||
cmd.spawn((
|
||||
Name::new("Scene"), // TODO: Maybe use the "basename" part of the asset path?
|
||||
name,
|
||||
SpatialBundle::default(),
|
||||
space_editor::prelude::GltfPrefab {
|
||||
path: path.0.clone(),
|
||||
path: event.path.clone(),
|
||||
scene: "Scene0".to_string(),
|
||||
},
|
||||
r_assets.load::<Gltf>(&path.0),
|
||||
r_assets.load::<Gltf>(&event.path),
|
||||
PrefabMarker,
|
||||
SceneRoot,
|
||||
));
|
||||
@@ -630,89 +643,3 @@ fn start_capture(mut captures: Query<&mut ScreenCapture, Added<ScreenCapture>>)
|
||||
capture.start_capturing();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
mod wasm {
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::{LoadLibraryObjectEvent, LoadingQueue, SceneState};
|
||||
use crate::wasm::{self, StaticBuffer, WasmMessageBuffer};
|
||||
|
||||
static OBJECT_LOAD_REQUEST: WasmMessageBuffer<String> = WasmMessageBuffer::new();
|
||||
|
||||
#[wasm_bindgen(js_name = loadObject)]
|
||||
pub fn load_object(id: &str) {
|
||||
OBJECT_LOAD_REQUEST.write_message(id.into());
|
||||
}
|
||||
|
||||
// FIXME: Is it an object or a scene? Inconsistent terminology is
|
||||
// unnecessarily confusing
|
||||
pub(super) fn dispatch_object_load_request(
|
||||
mut ew_load_scene: EventWriter<LoadLibraryObjectEvent>,
|
||||
) {
|
||||
if let Some(id) = OBJECT_LOAD_REQUEST.take_message() {
|
||||
ew_load_scene.send(LoadLibraryObjectEvent(id));
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const SCENE_STATE_EVENT: &str = r#"
|
||||
export interface SceneStateEvent extends CustomEvent {
|
||||
type: "scene-state";
|
||||
detail: SceneState;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface GlobalEventHandlersEventMap {
|
||||
"scene-state": SceneStateEvent;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_state_changes(
|
||||
r_state: Res<State<SceneState>>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> Result<(), String> {
|
||||
if let Ok(window) = q_window.get_single() {
|
||||
wasm::dispatch_to_js(window, "scene-state", &(**r_state as u32))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const LOADING_QUEUE_EVENT: &str = r#"
|
||||
export interface LoadingQueueEvent extends CustomEvent {
|
||||
type: "loading-queue";
|
||||
detail: number;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface GlobalEventHandlersEventMap {
|
||||
"loading-queue": LoadingQueueEvent;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_loading_queue_changes(
|
||||
mut l_last_sent: Local<Option<usize>>,
|
||||
r_loading: Res<LoadingQueue>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> Result<(), String> {
|
||||
let queue_len = r_loading.queue.len();
|
||||
if let Some(last_sent) = *l_last_sent {
|
||||
if last_sent == queue_len {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(window) = q_window.get_single() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
wasm::dispatch_to_js(window, "loading-queue", &queue_len)?;
|
||||
*l_last_sent = Some(queue_len);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::{ImportSceneEvent, LoadingQueue, SceneState};
|
||||
use crate::wasm::{self, StaticBuffer, WasmMessageBuffer};
|
||||
|
||||
static SPAWN_ASSETS: WasmMessageBuffer<Vec<AssetImport>> = WasmMessageBuffer::new();
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const ASSET_IMPORT_TS: &str = r#"
|
||||
export interface AssetImport {
|
||||
path: string;
|
||||
name?: string;
|
||||
}
|
||||
"#;
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct AssetImport {
|
||||
path: String,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const SPAWN_ASSET_TS: &str = r#"
|
||||
export function spawnAsset(asset: AssetImport): void;
|
||||
"#;
|
||||
#[wasm_bindgen(js_name = spawnAsset, skip_typescript)]
|
||||
pub fn spawn_asset(asset: JsValue) -> Result<(), JsValue> {
|
||||
let asset: AssetImport = serde_wasm_bindgen::from_value(asset)?;
|
||||
SPAWN_ASSETS.write_message(vec![asset]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const SPAWN_ASSETS_TS: &str = r#"
|
||||
export function spawnAssets(assets: AssetImport[]): void;
|
||||
"#;
|
||||
#[wasm_bindgen(js_name = spawnAssets, skip_typescript)]
|
||||
pub fn spawn_assets(assets: JsValue) -> Result<(), JsValue> {
|
||||
let assets: Vec<AssetImport> = serde_wasm_bindgen::from_value(assets)?;
|
||||
SPAWN_ASSETS.write_message(assets);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn handle_spawn_asset_messages(mut ew_import_scene: EventWriter<ImportSceneEvent>) {
|
||||
if let Some(assets) = SPAWN_ASSETS.take_message() {
|
||||
for AssetImport { path, name } in assets {
|
||||
ew_import_scene.send(ImportSceneEvent { path, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const SCENE_STATE_EVENT: &str = r#"
|
||||
export interface SceneStateEvent extends CustomEvent {
|
||||
type: "scene-state";
|
||||
detail: SceneState;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface GlobalEventHandlersEventMap {
|
||||
"scene-state": SceneStateEvent;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_state_changes(
|
||||
r_state: Res<State<SceneState>>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> Result<(), String> {
|
||||
if let Ok(window) = q_window.get_single() {
|
||||
wasm::dispatch_to_js(window, "scene-state", &(**r_state as u32))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const LOADING_QUEUE_EVENT: &str = r#"
|
||||
export interface LoadingQueueEvent extends CustomEvent {
|
||||
type: "loading-queue";
|
||||
detail: number;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface GlobalEventHandlersEventMap {
|
||||
"loading-queue": LoadingQueueEvent;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_loading_queue_changes(
|
||||
mut l_last_sent: Local<Option<usize>>,
|
||||
r_loading: Res<LoadingQueue>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> Result<(), String> {
|
||||
let queue_len = r_loading.queue.len();
|
||||
if let Some(last_sent) = *l_last_sent {
|
||||
if last_sent == queue_len {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(window) = q_window.get_single() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
wasm::dispatch_to_js(window, "loading-queue", &queue_len)?;
|
||||
*l_last_sent = Some(queue_len);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use space_editor::{
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::material::short_material::ShortMaterial;
|
||||
use crate::{
|
||||
interaction::Selection,
|
||||
wasm::{self, StaticBuffer, WasmMessageBuffer},
|
||||
@@ -91,7 +92,6 @@ impl Plugin for InspectorPlugin {
|
||||
.chain()
|
||||
.before(space_editor::space_prefab::sub_scene::prepare_auto_scene),
|
||||
);
|
||||
app.add_systems(PostUpdate, apply_short_materials);
|
||||
|
||||
app.world.resource_mut::<InspectGetters>().getters.insert(
|
||||
StandardMaterial::get_type_registration()
|
||||
@@ -139,25 +139,6 @@ fn create_short_materials(
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_short_materials(
|
||||
mut commands: Commands,
|
||||
q_mats: Query<(Entity, &Handle<StandardMaterial>, &ShortMaterial)>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
for (entity, handle, short) in q_mats.iter() {
|
||||
let Some(material) = materials.get_mut(handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
material.base_color = short.base_color;
|
||||
material.perceptual_roughness = short.perceptual_roughness;
|
||||
material.metallic = short.metallic;
|
||||
material.reflectance = short.reflectance;
|
||||
|
||||
commands.entity(entity).remove::<ShortMaterial>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct InspectGetters {
|
||||
getters: HashMap<
|
||||
@@ -588,26 +569,6 @@ fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
|
||||
ev_update_description.send(UpdateEntityDescription);
|
||||
}
|
||||
|
||||
#[derive(Reflect, Default, Clone, Component)]
|
||||
#[reflect(Default, Component)]
|
||||
struct ShortMaterial {
|
||||
pub base_color: Color,
|
||||
pub perceptual_roughness: f32,
|
||||
pub metallic: f32,
|
||||
pub reflectance: f32,
|
||||
}
|
||||
|
||||
impl ShortMaterial {
|
||||
pub fn new(material: &StandardMaterial) -> Self {
|
||||
Self {
|
||||
base_color: material.base_color,
|
||||
perceptual_roughness: material.perceptual_roughness,
|
||||
metallic: material.metallic,
|
||||
reflectance: material.reflectance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_standart_materal(
|
||||
r_selection: Option<Res<Selection>>,
|
||||
q_m: Query<&Handle<StandardMaterial>>,
|
||||
|
||||
Reference in New Issue
Block a user