veloren_voxygen/hud/
item_imgs.rs

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