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 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
79pub type ItemImagesSpec = Ron<HashMap<ItemKey, ImageSpec>>;
80
81pub 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 .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 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 let graphic = spec.create_graphic();
117 match self.map.get(kind) {
119 Some(id) => ui.replace_graphic(*id, graphic),
120 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 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
159fn 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}