#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
pub mod dzi_asset_loader;
mod ortho_ext;
mod systems;
use bevy::asset::AssetLoadError;
use bevy::platform::collections::HashSet;
use bevy::prelude::*;
use dzi_asset_loader::DziContents;
use std::sync::Arc;
pub const DEFAULT_MAX_CONCURRENT_TILE_LOADS: usize = 16;
pub const DEFAULT_ZOOM_LEVEL_BIAS: u32 = 2;
pub type TileId = (u16, u16, u16);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum DeepZoomInitialView {
#[default]
FitWidth,
None,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum DeepZoomPyramidDepth {
#[default]
OnePixel,
OneTile,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum DeepZoomLoadState {
#[default]
Loading,
Loaded,
Failed,
}
#[derive(Component, Debug, Clone)]
pub struct DeepZoom {
config: DeepZoomConfig,
state: DeepZoomState,
}
#[derive(Debug, Clone)]
pub struct DeepZoomConfig {
dzi_path: String,
tiles_base_path: String,
initial_view: DeepZoomInitialView,
pyramid_depth: DeepZoomPyramidDepth,
zoom_level_bias: u32,
tile_layer: f32,
draw_debug_ui: bool,
max_concurrent_tile_loads: usize,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct DeepZoomState {
dzi: Option<Handle<DziContents>>,
load_state: DeepZoomLoadState,
zoom_level: u32,
lowest_zoom_level: Option<u32>,
tiles_loading: HashSet<TileId>,
tiles_loaded: HashSet<TileId>,
}
impl DeepZoom {
pub fn from_config(config: DeepZoomConfig) -> Self {
Self {
config,
state: DeepZoomState::default(),
}
}
pub fn set_draw_debug_ui(&mut self, draw_debug_ui: bool) {
self.config.draw_debug_ui = draw_debug_ui;
}
pub fn load_state(&self) -> DeepZoomLoadState {
self.state.load_state
}
pub fn zoom_level(&self) -> u32 {
self.state.zoom_level
}
pub fn zoom_level_bias(&self) -> u32 {
self.config.zoom_level_bias
}
pub fn set_zoom_level_bias(&mut self, zoom_level_bias: u32) {
self.config.zoom_level_bias = zoom_level_bias;
}
}
impl DeepZoomConfig {
pub fn new(dzi_path: impl Into<String>, tiles_base_path: impl Into<String>) -> Self {
Self {
dzi_path: dzi_path.into(),
tiles_base_path: tiles_base_path.into(),
initial_view: Default::default(),
pyramid_depth: Default::default(),
zoom_level_bias: DEFAULT_ZOOM_LEVEL_BIAS,
tile_layer: 0.0,
draw_debug_ui: false,
max_concurrent_tile_loads: DEFAULT_MAX_CONCURRENT_TILE_LOADS,
}
}
pub fn with_tile_layer(mut self, tile_layer: f32) -> Self {
self.tile_layer = tile_layer;
self
}
pub fn with_initial_view(mut self, initial_view: DeepZoomInitialView) -> Self {
self.initial_view = initial_view;
self
}
pub fn with_pyramid_depth(mut self, pyramid_depth: DeepZoomPyramidDepth) -> Self {
self.pyramid_depth = pyramid_depth;
self
}
pub fn with_zoom_level_bias(mut self, zoom_level_bias: u32) -> Self {
self.zoom_level_bias = zoom_level_bias;
self
}
pub fn with_max_concurrent_tile_loads(mut self, max_concurrent_tile_loads: usize) -> Self {
self.max_concurrent_tile_loads = max_concurrent_tile_loads;
self
}
pub fn with_draw_debug_ui(mut self, draw_debug_ui: bool) -> Self {
self.draw_debug_ui = draw_debug_ui;
self
}
}
pub fn loaded_dzi<'a>(
deep_zoom: &'a DeepZoom,
dzi_assets: &'a Assets<DziContents>,
) -> Option<&'a DziContents> {
let dzi_handle = deep_zoom.state.dzi.as_ref()?;
dzi_assets.get(dzi_handle)
}
pub fn fit_width_scale(dzi: &DziContents, projection: &OrthographicProjection) -> f32 {
let width_at_scale_one = projection.area.width() / projection.scale;
dzi.size.width as f32 / width_at_scale_one
}
#[derive(Event, Debug, Clone, Copy)]
pub struct DeepZoomLoaded(pub Entity);
#[derive(Event, Debug, Clone)]
pub struct DeepZoomLoadFailed(pub Entity, pub Arc<AssetLoadError>);
#[derive(Component, Debug)]
pub(crate) struct DziTile {
pub owner_entity: Entity,
pub id: TileId,
pub zoom_level: u32,
pub rect: Rect,
}
#[derive(Default)]
pub struct DeepZoomPlugin;
impl Plugin for DeepZoomPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(dzi_asset_loader::plugin);
app.add_systems(
Update,
(
(systems::load_dzi_asset, systems::finish_loading_dzi).chain(),
systems::update_zoom_level,
systems::render_debug_ui,
systems::cleanup_orphaned_tiles,
(
systems::check_tile_loading_status,
systems::spawn_in_view_tiles,
systems::despawn_out_of_view_tiles,
)
.chain(),
),
);
}
}