veloren_voxygen/hud/
item_imgs.rs

1use crate::ui::{Graphic, SampleStrat, Transform, Ui};
2use common::{
3    assets::{self, AssetCombined, AssetExt, AssetHandle, Concatenate, DotVoxAsset, ReloadWatcher},
4    comp::item::item_key::ItemKey,
5    figure::Segment,
6};
7use conrod_core::image::Id;
8use hashbrown::HashMap;
9use image::DynamicImage;
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12use tracing::{error, warn};
13use vek::*;
14
15pub fn animate_by_pulse(ids: &[Id], pulse: f32) -> Id {
16    let animation_frame = (pulse * 3.0) as usize;
17    ids[animation_frame % ids.len()]
18}
19
20#[derive(Debug, Serialize, Deserialize)]
21pub enum ImageSpec {
22    Png(String),
23    Vox(String, #[serde(default)] u32),
24    // (specifier, offset, (axis, 2 * angle / pi), zoom, model_index)
25    VoxTrans(String, [f32; 3], [f32; 3], f32, #[serde(default)] u32),
26}
27impl ImageSpec {
28    pub fn create_graphic(&self) -> Graphic {
29        match self {
30            ImageSpec::Png(specifier) => Graphic::Image(graceful_load_img(specifier), None),
31            ImageSpec::Vox(specifier, model_index) => Graphic::Voxel(
32                graceful_load_segment_no_skin(specifier, *model_index),
33                Transform {
34                    stretch: false,
35                    ..Default::default()
36                },
37                SampleStrat::None,
38            ),
39            ImageSpec::VoxTrans(specifier, offset, [rot_x, rot_y, rot_z], zoom, model_index) => {
40                Graphic::Voxel(
41                    graceful_load_segment_no_skin(specifier, *model_index),
42                    Transform {
43                        ori: Quaternion::rotation_x(rot_x * std::f32::consts::PI / 180.0)
44                            .rotated_y(rot_y * std::f32::consts::PI / 180.0)
45                            .rotated_z(rot_z * std::f32::consts::PI / 180.0),
46                        offset: Vec3::from(*offset),
47                        zoom: *zoom,
48                        orth: true, // TODO: Is this what we want here? @Pfau
49                        stretch: false,
50                    },
51                    SampleStrat::None,
52                )
53            },
54        }
55    }
56}
57
58#[derive(Serialize, Deserialize)]
59pub struct ItemImagesSpec(pub HashMap<ItemKey, ImageSpec>);
60impl assets::Asset for ItemImagesSpec {
61    type Loader = assets::RonLoader;
62
63    const EXTENSION: &'static str = "ron";
64}
65impl Concatenate for ItemImagesSpec {
66    fn concatenate(self, b: Self) -> Self { ItemImagesSpec(self.0.concatenate(b.0)) }
67}
68
69// TODO: when there are more images don't load them all into memory
70pub struct ItemImgs {
71    map: HashMap<ItemKey, Id>,
72    manifest: AssetHandle<ItemImagesSpec>,
73    watcher: ReloadWatcher,
74    not_found: Id,
75}
76
77impl ItemImgs {
78    pub fn new(ui: &mut Ui, not_found: Id) -> Self {
79        let manifest = ItemImagesSpec::load_expect_combined_static("voxygen.item_image_manifest");
80        let map = manifest
81            .read()
82            .0
83            .iter()
84            // TODO: what if multiple kinds map to the same image, it would be nice to use the same
85            // image id for both, although this does interfere with the current hot-reloading
86            // strategy
87            .map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
88            .collect();
89
90        Self {
91            map,
92            manifest,
93            watcher: manifest.reload_watcher(),
94            not_found,
95        }
96    }
97
98    /// Checks if the manifest has been changed and reloads the images if so
99    /// Reuses img ids
100    pub fn reload_if_changed(&mut self, ui: &mut Ui) {
101        if self.watcher.reloaded() {
102            for (kind, spec) in self.manifest.read().0.iter() {
103                // Load new graphic
104                let graphic = spec.create_graphic();
105                // See if we already have an id we can use
106                match self.map.get(kind) {
107                    Some(id) => ui.replace_graphic(*id, graphic),
108                    // Otherwise, generate new id and insert it into our Id -> ItemKey map
109                    None => {
110                        self.map.insert(kind.clone(), ui.add_graphic(graphic));
111                    },
112                }
113            }
114        }
115    }
116
117    pub fn img_ids(&self, item_key: ItemKey) -> Vec<Id> {
118        if let ItemKey::TagExamples(keys, _) = item_key {
119            return keys
120                .iter()
121                .filter_map(|k| self.map.get(k))
122                .cloned()
123                .collect();
124        };
125        match self.map.get(&item_key) {
126            Some(id) => vec![*id],
127            // There was no specification in the ron
128            None => {
129                warn!(
130                    ?item_key,
131                    "missing specified image file (note: hot-reloading won't work here)",
132                );
133                Vec::new()
134            },
135        }
136    }
137
138    pub fn img_ids_or_not_found_img(&self, item_key: ItemKey) -> Vec<Id> {
139        let mut ids = self.img_ids(item_key);
140        if ids.is_empty() {
141            ids.push(self.not_found)
142        }
143        ids
144    }
145}
146
147// Copied from figure/load.rs
148// TODO: remove code dup?
149fn graceful_load_vox(specifier: &str) -> AssetHandle<DotVoxAsset> {
150    let full_specifier: String = ["voxygen.", specifier].concat();
151    match DotVoxAsset::load(full_specifier.as_str()) {
152        Ok(dot_vox) => dot_vox,
153        Err(_) => {
154            error!(?full_specifier, "Could not load vox file for item images",);
155            DotVoxAsset::load_expect("voxygen.voxel.not_found")
156        },
157    }
158}
159fn graceful_load_img(specifier: &str) -> Arc<DynamicImage> {
160    let full_specifier: String = ["voxygen.", specifier].concat();
161    let handle = match assets::Image::load(&full_specifier) {
162        Ok(img) => img,
163        Err(_) => {
164            error!(?full_specifier, "Could not load image file for item images");
165            assets::Image::load_expect("voxygen.element.not_found")
166        },
167    };
168    handle.read().to_image()
169}
170
171fn graceful_load_segment_no_skin(specifier: &str, model_index: u32) -> Arc<Segment> {
172    use common::figure::{MatSegment, mat_cell::MatCell};
173    let mat_seg = MatSegment::from_vox_model_index(
174        &graceful_load_vox(specifier).read().0,
175        model_index as usize,
176    );
177    let seg = mat_seg
178        .map(|mat_cell| match mat_cell {
179            MatCell::None => None,
180            MatCell::Mat(_) => Some(MatCell::None),
181            MatCell::Normal(data) => data.is_hollow().then_some(MatCell::None),
182        })
183        .to_segment(|_| Default::default());
184    Arc::new(seg)
185}