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::{self, AssetCombined, AssetExt, AssetHandle, Concatenate, DotVoxAsset, ReloadWatcher},
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
79#[derive(Serialize, Deserialize)]
80pub struct ItemImagesSpec(pub HashMap<ItemKey, ImageSpec>);
81impl assets::Asset for ItemImagesSpec {
82    type Loader = assets::RonLoader;
83
84    const EXTENSION: &'static str = "ron";
85}
86impl Concatenate for ItemImagesSpec {
87    fn concatenate(self, b: Self) -> Self { ItemImagesSpec(self.0.concatenate(b.0)) }
88}
89
90// TODO: when there are more images don't load them all into memory
91pub struct ItemImgs {
92    map: HashMap<ItemKey, Id>,
93    manifest: AssetHandle<ItemImagesSpec>,
94    watcher: ReloadWatcher,
95    not_found: Id,
96}
97
98impl ItemImgs {
99    pub fn new(ui: &mut Ui, not_found: Id) -> Self {
100        let manifest = ItemImagesSpec::load_expect_combined_static("voxygen.item_image_manifest");
101        let map = manifest
102            .read()
103            .0
104            .iter()
105            // TODO: what if multiple kinds map to the same image, it would be nice to use the same
106            // image id for both, although this does interfere with the current hot-reloading
107            // strategy
108            .map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
109            .collect();
110
111        Self {
112            map,
113            manifest,
114            watcher: manifest.reload_watcher(),
115            not_found,
116        }
117    }
118
119    /// Checks if the manifest has been changed and reloads the images if so
120    /// Reuses img ids
121    pub fn reload_if_changed(&mut self, ui: &mut Ui) {
122        if self.watcher.reloaded() {
123            for (kind, spec) in self.manifest.read().0.iter() {
124                // Load new graphic
125                let graphic = spec.create_graphic();
126                // See if we already have an id we can use
127                match self.map.get(kind) {
128                    Some(id) => ui.replace_graphic(*id, graphic),
129                    // Otherwise, generate new id and insert it into our Id -> ItemKey map
130                    None => {
131                        self.map.insert(kind.clone(), ui.add_graphic(graphic));
132                    },
133                }
134            }
135        }
136    }
137
138    pub fn img_ids(&self, item_key: ItemKey) -> Vec<Id> {
139        if let ItemKey::TagExamples(keys, _) = item_key {
140            return keys
141                .iter()
142                .filter_map(|k| self.map.get(k))
143                .cloned()
144                .collect();
145        };
146        match self.map.get(&item_key) {
147            Some(id) => vec![*id],
148            // There was no specification in the ron
149            None => {
150                warn!(
151                    ?item_key,
152                    "missing specified image file (note: hot-reloading won't work here)",
153                );
154                Vec::new()
155            },
156        }
157    }
158
159    pub fn img_ids_or_not_found_img(&self, item_key: ItemKey) -> Vec<Id> {
160        let mut ids = self.img_ids(item_key);
161        if ids.is_empty() {
162            ids.push(self.not_found)
163        }
164        ids
165    }
166}
167
168// Copied from figure/load.rs
169// TODO: remove code dup?
170fn graceful_load_vox(specifier: &str) -> AssetHandle<DotVoxAsset> {
171    let full_specifier: String = ["voxygen.", specifier].concat();
172    match DotVoxAsset::load(full_specifier.as_str()) {
173        Ok(dot_vox) => dot_vox,
174        Err(_) => {
175            error!(?full_specifier, "Could not load vox file for item images",);
176            DotVoxAsset::load_expect("voxygen.voxel.not_found")
177        },
178    }
179}
180fn graceful_load_img(specifier: &str) -> Arc<DynamicImage> {
181    let full_specifier: String = ["voxygen.", specifier].concat();
182    let handle = match assets::Image::load(&full_specifier) {
183        Ok(img) => img,
184        Err(_) => {
185            error!(?full_specifier, "Could not load image file for item images");
186            assets::Image::load_expect("voxygen.element.not_found")
187        },
188    };
189    handle.read().to_image()
190}
191
192fn graceful_load_segment_no_skin(
193    specifier: &str,
194    model_index: u32,
195    color: Option<[u8; 3]>,
196) -> Arc<Segment> {
197    use common::figure::{MatSegment, mat_cell::MatCell};
198    let mut mat_seg = MatSegment::from_vox_model_index(
199        &graceful_load_vox(specifier).read().0,
200        model_index as usize,
201    );
202
203    if let Some(color) = color {
204        let color = Vec3::from(color);
205        mat_seg = mat_seg.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(color)));
206    }
207
208    let seg = mat_seg
209        .map(|mat_cell| match mat_cell {
210            MatCell::None => None,
211            MatCell::Mat(_) => Some(MatCell::None),
212            MatCell::Normal(data) => data.attr.is_hollow().then_some(MatCell::None),
213        })
214        .to_segment(|_| Default::default());
215    Arc::new(seg)
216}