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 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, 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
90pub 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 .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 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 let graphic = spec.create_graphic();
126 match self.map.get(kind) {
128 Some(id) => ui.replace_graphic(*id, graphic),
129 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 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
168fn 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}