diff --git a/CHANGELOG.md b/CHANGELOG.md index 0358de34..57adde49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ This release supports Bevy version 0.14 and has an [MSRV][] of 1.80. ### Changed - bevy_vello now uses Bevy 0.15 +- `VelloAsset` assets have been separated into `VelloSvg` and `VelloLottie` +- `VelloAssetBundle` has been separated into `VelloSvgBundle` and `VelloLottieBundle` +- `Handle` has been separated into `VelloSvgHandle` and `VelloLottieHandle` +- `VelloAssetAnchor` has been separated into `VelloSvgAnchor` and `VelloLottieAnchor` - The license on bevy_vello no longer includes OFL 1.1 ### Fixed @@ -265,4 +269,4 @@ This release supports Bevy version 0.13 and has an [MSRV][] of 1.77. [0.1.1]: https://github.com/linebender/bevy_vello/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/linebender/bevy_vello/releases/tag/v0.1.0 -[MSRV]: README.md#minimum-supported-rust-version-msrv +[MSRV]: README.md#minimum-supported-rust-version-msrv \ No newline at end of file diff --git a/examples/demo/src/main.rs b/examples/demo/src/main.rs index 9c101839..a2fe2efd 100644 --- a/examples/demo/src/main.rs +++ b/examples/demo/src/main.rs @@ -27,10 +27,8 @@ fn main() { fn setup_vector_graphics(mut commands: Commands, asset_server: ResMut) { commands.spawn((Camera2d, bevy_pancam::PanCam::default())); commands - .spawn(VelloAssetBundle { - asset: VelloAssetHandle( - asset_server.load::("embedded://demo/assets/calendar.json"), - ), + .spawn(VelloLottieBundle { + asset: VelloLottieHandle(asset_server.load("embedded://demo/assets/calendar.json")), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)) .with_scale(Vec3::splat(20.0)), debug_visualizations: DebugVisualizations::Visible, @@ -74,19 +72,16 @@ fn setup_vector_graphics(mut commands: Commands, asset_server: ResMut>, - assets: Res>, + mut asset_ev: EventReader>, + assets: Res>, ) { for ev in asset_ev.read() { if let AssetEvent::LoadedWithDependencies { id } = ev { let asset = assets.get(*id).unwrap(); - #[allow(irrefutable_let_patterns)] - if let VectorFile::Lottie(composition) = &asset.file { - info!( - "Animated asset loaded. Layers:\n{:#?}", - composition.as_ref().get_layers().collect::>() - ); - } + info!( + "Animated asset loaded. Layers:\n{:#?}", + asset.composition.as_ref().get_layers().collect::>() + ); } } } diff --git a/examples/demo/src/ui.rs b/examples/demo/src/ui.rs index fa2fb349..b5c0f50f 100644 --- a/examples/demo/src/ui.rs +++ b/examples/demo/src/ui.rs @@ -13,9 +13,9 @@ pub fn controls_ui( &mut Playhead, &mut PlaybackOptions, &mut Theme, - &VelloAssetHandle, + &VelloLottieHandle, )>, - assets: Res>, + assets: Res>, ) { let Ok((mut player, mut playhead, mut options, mut theme, handle)) = player.get_single_mut() else { @@ -23,11 +23,7 @@ pub fn controls_ui( }; let asset = assets.get(handle.id()).unwrap(); - #[allow(irrefutable_let_patterns)] - let VectorFile::Lottie(composition) = &asset.file - else { - return; - }; + let composition = asset.composition.as_ref(); let window = egui::Window::new("Controls") .resizable(false) @@ -250,7 +246,7 @@ pub fn controls_ui( }); ui.heading("Theme"); - for layer in composition.as_ref().get_layers() { + for layer in composition.get_layers() { let color = theme.get_mut(layer).cloned().unwrap_or_default(); let color = color.to_srgba().to_u8_array(); let mut color32 = diff --git a/examples/drag_n_drop/src/main.rs b/examples/drag_n_drop/src/main.rs index ee1a61d3..ece36389 100644 --- a/examples/drag_n_drop/src/main.rs +++ b/examples/drag_n_drop/src/main.rs @@ -3,6 +3,7 @@ use bevy::{ prelude::*, }; use bevy_vello::{prelude::*, VelloPlugin}; +use std::ffi::OsStr; fn main() { let mut app = App::new(); @@ -19,10 +20,9 @@ fn main() { fn setup_vector_graphics(mut commands: Commands, asset_server: ResMut) { commands.spawn(Camera2d); - commands.spawn(VelloAssetBundle { - asset: VelloAssetHandle( - asset_server.load::("embedded://drag_n_drop/assets/fountain.svg"), - ), + + commands.spawn(VelloSvgBundle { + asset: VelloSvgHandle(asset_server.load("embedded://drag_n_drop/assets/fountain.svg")), debug_visualizations: DebugVisualizations::Visible, transform: Transform::from_scale(Vec3::splat(5.0)), ..default() @@ -32,18 +32,37 @@ fn setup_vector_graphics(mut commands: Commands, asset_server: ResMut, + mut commands: Commands, + query_lottie: Option>>, + query_svg: Option>>, asset_server: ResMut, mut dnd_evr: EventReader, ) { - let Ok(mut asset) = query.get_single_mut() else { - return; - }; for ev in dnd_evr.read() { + if let Some(ref svg) = query_svg { + commands.entity(**svg).despawn(); + } + if let Some(ref lottie) = query_lottie { + commands.entity(**lottie).despawn(); + } let FileDragAndDrop::DroppedFile { path_buf, .. } = ev else { continue; }; - let new_handle = VelloAssetHandle(asset_server.load(path_buf.clone())); - *asset = new_handle; + let Some(ext) = path_buf.extension() else { + continue; + }; + let svg_ext = OsStr::new("svg"); + let lottie_ext = OsStr::new("json"); + if ext == svg_ext { + commands.spawn(VelloSvgBundle { + asset: VelloSvgHandle(asset_server.load(path_buf.clone())), + ..default() + }); + } else if ext == lottie_ext { + commands.spawn(VelloLottieBundle { + asset: VelloLottieHandle(asset_server.load(path_buf.clone())), + ..default() + }); + } } } diff --git a/examples/lottie/src/main.rs b/examples/lottie/src/main.rs index f97cbda2..b619d3ad 100644 --- a/examples/lottie/src/main.rs +++ b/examples/lottie/src/main.rs @@ -20,8 +20,8 @@ fn load_lottie(mut commands: Commands, asset_server: ResMut) { commands.spawn(Camera2d); // Yes, it's this simple. - commands.spawn(VelloAssetBundle { - asset: VelloAssetHandle(asset_server.load("embedded://lottie/assets/Tiger.json")), + commands.spawn(VelloLottieBundle { + asset: VelloLottieHandle(asset_server.load("embedded://lottie/assets/Tiger.json")), debug_visualizations: DebugVisualizations::Visible, transform: Transform::from_scale(Vec3::splat(0.5)), ..default() diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 07536e57..9015aee5 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -20,8 +20,8 @@ fn load_svg(mut commands: Commands, asset_server: ResMut) { commands.spawn(Camera2d); // Yes, it's this simple. - commands.spawn(VelloAssetBundle { - asset: VelloAssetHandle(asset_server.load("embedded://svg/assets/fountain.svg")), + commands.spawn(VelloSvgBundle { + asset: VelloSvgHandle(asset_server.load("embedded://svg/assets/fountain.svg")), debug_visualizations: DebugVisualizations::Visible, transform: Transform::from_scale(Vec3::splat(5.0)), ..default() diff --git a/src/debug.rs b/src/debug.rs index 4655d248..52b81ad3 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,8 +1,5 @@ //! Logic for rendering debug visualizations -use crate::{ - text::VelloTextAnchor, CoordinateSpace, VelloAsset, VelloAssetAnchor, VelloAssetHandle, - VelloFont, VelloTextSection, -}; +use crate::prelude::*; use bevy::{color::palettes::css, math::Vec3Swizzles, prelude::*}; const RED_X_SIZE: f32 = 8.0; @@ -11,7 +8,15 @@ pub struct DebugVisualizationsPlugin; impl Plugin for DebugVisualizationsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Update, (render_asset_debug, render_text_debug)); + // TODO: Would be great if we could render scene debug, but Vello doesn't tell us the AABB or BB. + + app.add_systems(Update, render_text_debug); + + #[cfg(feature = "svg")] + app.add_systems(Update, render_svg_debug); + + #[cfg(feature = "lottie")] + app.add_systems(Update, render_lottie_debug); } } @@ -23,19 +28,81 @@ pub enum DebugVisualizations { Visible, } -/// A system to render debug visualizations for `VelloAsset`. -fn render_asset_debug( +#[cfg(feature = "svg")] +/// A system to render debug visualizations for SVGs. +fn render_svg_debug( + query_vectors: Query< + ( + &VelloSvgHandle, + &VelloSvgAnchor, + &GlobalTransform, + &CoordinateSpace, + &DebugVisualizations, + ), + Without, + >, + assets: Res>, + query_cam: Query<(&Camera, &GlobalTransform, &OrthographicProjection), With>, + mut gizmos: Gizmos, +) { + let Ok((camera, view, projection)) = query_cam.get_single() else { + return; + }; + + // Show vectors + for (asset, asset_anchor, gtransform, space, _) in query_vectors + .iter() + .filter(|(_, _, _, _, d)| **d == DebugVisualizations::Visible) + { + if let Some(asset) = assets.get(asset.id()) { + match space { + CoordinateSpace::WorldSpace => { + // Origin + let origin = gtransform.translation().xy(); + draw_origin(&mut gizmos, projection, origin); + // Bounding box + let gtransform = &asset_anchor.compute(asset.width, asset.height, gtransform); + let rect_center = gtransform.translation().xy(); + let rect = asset.bb_in_world_space(gtransform); + draw_bounding_box(&mut gizmos, rect_center, rect.size()); + } + CoordinateSpace::ScreenSpace => { + // Origin + let origin = gtransform.translation().xy(); + let Ok(origin) = camera.viewport_to_world_2d(view, origin) else { + continue; + }; + draw_origin(&mut gizmos, projection, origin); + // Bounding box + let gtransform = &asset_anchor.compute(asset.width, asset.height, gtransform); + let rect_center = gtransform.translation().xy(); + let Ok(rect_center) = camera.viewport_to_world_2d(view, rect_center) else { + continue; + }; + let Some(rect) = asset.bb_in_screen_space(gtransform, camera, view) else { + continue; + }; + draw_bounding_box(&mut gizmos, rect_center, rect.size()); + } + } + } + } +} + +#[cfg(feature = "lottie")] +/// A system to render debug visualizations for SVGs. +fn render_lottie_debug( query_vectors: Query< ( - &VelloAssetHandle, - &VelloAssetAnchor, + &VelloLottieHandle, + &VelloLottieAnchor, &GlobalTransform, &CoordinateSpace, &DebugVisualizations, ), Without, >, - assets: Res>, + assets: Res>, query_cam: Query<(&Camera, &GlobalTransform, &OrthographicProjection), With>, mut gizmos: Gizmos, ) { @@ -55,7 +122,7 @@ fn render_asset_debug( let origin = gtransform.translation().xy(); draw_origin(&mut gizmos, projection, origin); // Bounding box - let gtransform = &asset_anchor.compute(asset, gtransform); + let gtransform = &asset_anchor.compute(asset.width, asset.height, gtransform); let rect_center = gtransform.translation().xy(); let rect = asset.bb_in_world_space(gtransform); draw_bounding_box(&mut gizmos, rect_center, rect.size()); @@ -68,7 +135,7 @@ fn render_asset_debug( }; draw_origin(&mut gizmos, projection, origin); // Bounding box - let gtransform = &asset_anchor.compute(asset, gtransform); + let gtransform = &asset_anchor.compute(asset.width, asset.height, gtransform); let rect_center = gtransform.translation().xy(); let Ok(rect_center) = camera.viewport_to_world_2d(view, rect_center) else { continue; @@ -222,6 +289,7 @@ fn draw_origin(gizmos: &mut Gizmos, projection: &OrthographicProjection, origin: gizmos.line_2d(from, to, css::RED); } +#[cfg(any(feature = "svg", feature = "lottie"))] /// A helper method to draw the bounding box fn draw_bounding_box(gizmos: &mut Gizmos, position: Vec2, size: Vec2) { gizmos.rect_2d( diff --git a/src/integrations/asset.rs b/src/integrations/asset.rs deleted file mode 100644 index e6076701..00000000 --- a/src/integrations/asset.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::VectorFile; -use bevy::{prelude::*, reflect::TypePath}; - -#[derive(Component, Default, Debug, Clone, Deref, DerefMut, PartialEq, Eq)] -pub struct VelloAssetHandle(pub Handle); - -#[derive(Asset, TypePath, Clone)] -pub struct VelloAsset { - pub file: VectorFile, - pub local_transform_center: Transform, - pub width: f32, - pub height: f32, - pub alpha: f32, -} - -impl VelloAsset { - /// Returns the bounding box in world space - pub fn bb_in_world_space(&self, gtransform: &GlobalTransform) -> Rect { - // Convert local coordinates to world coordinates - let local_min = Vec3::new(-self.width / 2.0, -self.height / 2.0, 0.0).extend(1.0); - let local_max = Vec3::new(self.width / 2.0, self.height / 2.0, 0.0).extend(1.0); - - let min_world = gtransform.compute_matrix() * local_min; - let max_world = gtransform.compute_matrix() * local_max; - - // Calculate the distance between the vertices to get the size in world space - let min = Vec2::new(min_world.x, min_world.y); - let max = Vec2::new(max_world.x, max_world.y); - Rect { min, max } - } - - /// Returns the bounding box in screen space - pub fn bb_in_screen_space( - &self, - gtransform: &GlobalTransform, - camera: &Camera, - camera_transform: &GlobalTransform, - ) -> Option { - let Rect { min, max } = self.bb_in_world_space(gtransform); - camera - .viewport_to_world_2d(camera_transform, min) - .ok() - .zip(camera.viewport_to_world_2d(camera_transform, max).ok()) - .map(|(min, max)| Rect { min, max }) - } -} - -/// Describes how the asset is positioned relative to its [`Transform`]. It defaults to [`VelloAssetAnchor::Center`]. -#[derive(Component, Default, Clone, Copy, PartialEq, Eq)] -pub enum VelloAssetAnchor { - /// Bounds start from the render position and advance up and to the right. - BottomLeft, - /// Bounds start from the render position and advance up. - Bottom, - /// Bounds start from the render position and advance up and to the left. - BottomRight, - - /// Bounds start from the render position and advance right. - Left, - /// Bounds start from the render position and advance equally on both axes. - #[default] - Center, - /// Bounds start from the render position and advance left. - Right, - - /// Bounds start from the render position and advance down and to the right. - TopLeft, - /// Bounds start from the render position and advance down. - Top, - /// Bounds start from the render position and advance down and to the left. - TopRight, -} - -impl VelloAssetAnchor { - pub(crate) fn compute( - &self, - asset: &VelloAsset, - transform: &GlobalTransform, - ) -> GlobalTransform { - let (width, height) = (asset.width, asset.height); - // Apply positioning - let adjustment = match self { - VelloAssetAnchor::TopLeft => Vec3::new(width / 2.0, -height / 2.0, 0.0), - VelloAssetAnchor::Left => Vec3::new(width / 2.0, 0.0, 0.0), - VelloAssetAnchor::BottomLeft => Vec3::new(width / 2.0, height / 2.0, 0.0), - VelloAssetAnchor::Top => Vec3::new(0.0, -height / 2.0, 0.0), - VelloAssetAnchor::Center => Vec3::new(0.0, 0.0, 0.0), - VelloAssetAnchor::Bottom => Vec3::new(0.0, height / 2.0, 0.0), - VelloAssetAnchor::TopRight => Vec3::new(-width / 2.0, -height / 2.0, 0.0), - VelloAssetAnchor::Right => Vec3::new(-width / 2.0, 0.0, 0.0), - VelloAssetAnchor::BottomRight => Vec3::new(-width / 2.0, height / 2.0, 0.0), - }; - let new_translation: Vec3 = (transform.compute_matrix() * adjustment.extend(1.0)).xyz(); - GlobalTransform::from( - transform - .compute_transform() - .with_translation(new_translation), - ) - } -} diff --git a/src/integrations/dot_lottie/player_state.rs b/src/integrations/dot_lottie/player_state.rs index 81849dfd..9764f9ef 100644 --- a/src/integrations/dot_lottie/player_state.rs +++ b/src/integrations/dot_lottie/player_state.rs @@ -1,10 +1,10 @@ use super::PlayerTransition; -use crate::{PlaybackOptions, Theme, VelloAssetHandle}; +use crate::{integrations::lottie::asset::VelloLottieHandle, PlaybackOptions, Theme}; #[derive(Debug, Clone)] pub struct PlayerState { pub id: &'static str, - pub asset: Option, + pub asset: Option, pub theme: Option, pub options: Option, pub transitions: Vec, @@ -27,7 +27,7 @@ impl PlayerState { } } - pub fn asset(mut self, asset: VelloAssetHandle) -> Self { + pub fn asset(mut self, asset: VelloLottieHandle) -> Self { self.asset.replace(asset); self } @@ -57,7 +57,7 @@ impl PlayerState { self } - pub fn set_asset(mut self, asset: Option) -> Self { + pub fn set_asset(mut self, asset: Option) -> Self { self.asset = asset; self } @@ -87,7 +87,7 @@ impl PlayerState { self } - pub fn get_asset(&self) -> Option<&VelloAssetHandle> { + pub fn get_asset(&self) -> Option<&VelloLottieHandle> { self.asset.as_ref() } diff --git a/src/integrations/dot_lottie/systems.rs b/src/integrations/dot_lottie/systems.rs index f6672d89..3ed3bd8b 100644 --- a/src/integrations/dot_lottie/systems.rs +++ b/src/integrations/dot_lottie/systems.rs @@ -1,7 +1,10 @@ use super::DotLottiePlayer; use crate::{ - integrations::lottie::PlaybackPlayMode, PlaybackDirection, PlaybackLoopBehavior, - PlaybackOptions, PlayerTransition, Playhead, VectorFile, VelloAsset, VelloAssetHandle, + integrations::lottie::{ + asset::{VelloLottie, VelloLottieHandle}, + PlaybackPlayMode, + }, + PlaybackDirection, PlaybackLoopBehavior, PlaybackOptions, PlayerTransition, Playhead, }; use bevy::{prelude::*, utils::Instant}; use std::time::Duration; @@ -10,27 +13,27 @@ use vello_svg::usvg::strict_num::Ulps; /// Advance all the dotLottie playheads in the scene pub fn advance_dot_lottie_playheads( mut query: Query<( - &VelloAssetHandle, + &VelloLottieHandle, &mut Playhead, &mut DotLottiePlayer, &PlaybackOptions, )>, - mut assets: ResMut>, + mut assets: ResMut>, time: Res