1use std::f32::consts::PI;
2
3use crate::{
4 font::Font,
5 framework::StepperId,
6 material::Material,
7 maths::{Bounds, Matrix, Pose, Quat, Vec2, Vec3},
8 mesh::{Inds, Mesh, Vertex},
9 sk::MainThreadToken,
10 sound::{Sound, SoundInst},
11 sprite::Sprite,
12 system::{Input, Renderer, Text, TextStyle},
13 tex::Tex,
14 ui::{Ui, UiBtnLayout, UiMove, UiWin},
15 util::named_colors::RED,
16};
17
18pub struct ScreenRepo {
19 id_btn_show_hide_param: String,
20 id_window_param: String,
21 show_param: bool,
22 sprite_hide_param: Sprite,
23 sprite_show_param: Sprite,
24 id_handle: String,
25 id_material: String,
26 id_texture: String,
27 id_left_sound: String,
28 id_right_sound: String,
29 id_slider_distance: String,
30 id_slider_size: String,
31 id_slider_flattening: String,
32}
33
34impl ScreenRepo {
35 pub fn new(id: String) -> Self {
36 Self {
37 show_param: false,
38 sprite_hide_param: Sprite::close(),
39 sprite_show_param: Sprite::from_file("icons/hamburger.png", None, None).unwrap_or_default(),
40 id_btn_show_hide_param: id.clone() + "_btn_show_hide",
41 id_window_param: id.clone() + "_window_param",
42 id_handle: id.clone() + "_handle",
43 id_material: id.clone() + "_material",
44 id_texture: id.clone() + "_texture",
45 id_left_sound: id.clone() + "_left_sound",
46 id_right_sound: id.clone() + "_right_sound",
47 id_slider_distance: id.clone() + "_slider_distance",
48 id_slider_size: id.clone() + "_slider_size",
49 id_slider_flattening: id.clone() + "_slider_radius",
50 }
51 }
52}
53
54pub struct Screen {
56 id: StepperId,
57
58 repo: ScreenRepo,
59 pub width: i32,
60 pub height: i32,
61 pub screen_distance: f32,
62 pub screen_flattening: f32,
63 pub screen_size: Vec2,
64 pub screen_diagonal: f32,
65 pub screen_pose: Pose,
66 pub screen: Mesh,
67 pub sound_spacing_factor: f32,
68 pub text: String,
69 pub transform: Matrix,
70 pub text_style: Option<TextStyle>,
71 screen_material: Material,
72
73 sound_left: Sound,
74 sound_left_inst: Option<SoundInst>,
75 sound_right: Sound,
76 sound_right_inst: Option<SoundInst>,
77}
78
79unsafe impl Send for Screen {}
80
81impl Default for Screen {
83 fn default() -> Self {
84 let screen_size = Vec2::new(3.840, 2.160);
85 let screen_diagonal = (screen_size.x.powf(2.0) + screen_size.y.powf(2.0)).sqrt();
86 let screen_material = Material::unlit().copy();
87
88 Self {
89 id: "Screen1".to_string(),
90
91 repo: ScreenRepo::new("Screen1".to_string()),
92 width: 3840,
93 height: 2160,
94 screen_distance: 2.20,
95 screen_flattening: 0.99,
96 screen_size,
97 screen_diagonal,
98 screen_pose: Pose::IDENTITY,
99 screen: Mesh::new(),
100 sound_spacing_factor: 3.0,
101 text: "Screen1".to_owned(),
102 transform: Matrix::t_r(
103 Vec3::new(0.0, 2.0, -2.5), Quat::from_angles(0.0, 180.0, 0.0),
105 ),
106 text_style: Some(Text::make_style(Font::default(), 0.3, RED)),
107 screen_material,
108
109 sound_left: Sound::click(),
110 sound_left_inst: None,
111 sound_right: Sound::click(),
112 sound_right_inst: None,
113 }
114 }
115}
116
117impl Screen {
119 pub fn new(id: &str, screen_tex: impl AsRef<Tex>) -> Self {
121 let mut this = Self { ..Default::default() };
122
123 this.id = id.to_string();
124 this.repo = ScreenRepo::new(this.id.clone());
125
126 let screen_tex = screen_tex.as_ref().clone_ref();
127
128 this.repo.id_texture = screen_tex.as_ref().get_id().to_string();
129 this.screen_material.id(&this.repo.id_material).diffuse_tex(&screen_tex);
130
131 this.sound_left = Sound::create_stream(200.0).unwrap_or_default();
132 this.sound_left.id(&this.repo.id_left_sound);
133 this.sound_right = Sound::create_stream(200.0).unwrap_or_default();
134 this.sound_right.id(&this.repo.id_right_sound);
135
136 this.screen_pose = Input::get_head() * Matrix::r(Quat::from_angles(0.0, 180.0, 0.0));
137 this.adapt_screen();
138
139 this.sound_left_inst = Some(this.sound_left.play(this.sound_position(-1), Some(1.0)));
140 this.sound_right_inst = Some(this.sound_right.play(this.sound_position(1), Some(1.0)));
141
142 this
143 }
144
145 pub fn draw(&mut self, token: &MainThreadToken) {
147 let screen_transform = self.screen_param();
148
149 Renderer::add_mesh(token, &self.screen, &self.screen_material, screen_transform, None, None);
150
151 Text::add_at(token, &self.text, self.transform, self.text_style, None, None, None, None, None, None);
152 }
153
154 fn screen_param(&mut self) -> Matrix {
156 const GRAB_X_MARGIN: f32 = 0.4;
157
158 const MAX_DISTANCE: f32 = 6.0;
159
160 const MAX_DIAGONAL: f32 = 15.0;
161 const MIN_DIAGONAL: f32 = 0.2;
162 let bounds = self.screen.get_bounds();
163
164 let factor_size = (self.screen_distance.max(1.0).powf(2.0) + self.screen_diagonal.max(1.0).powf(2.0)).sqrt();
165
166 let grab_position = Vec3::new(
167 0.0, self.screen_size.y / 2.0 + 0.05 * factor_size,
169 bounds.center.z,
170 );
171 let grab_dimension = Vec3::new(
172 factor_size * 0.2, factor_size * 0.01,
174 factor_size * 0.01,
175 );
176 if Ui::handle(
177 &self.repo.id_handle,
178 &mut self.screen_pose,
179 Bounds::new(grab_position, grab_dimension),
180 true,
181 Some(UiMove::Exact),
182 None,
183 ) {
184 let head = Input::get_head();
185 self.screen_pose.position = head.position;
186 }
187 let screen_transform = self.screen_pose.to_matrix(None);
188
189 let mut adapt = false;
190 if self.repo.show_param {
191 let info_position = Vec3::new(bounds.center.x, bounds.center.y, GRAB_X_MARGIN * 1.5);
192 let mut window_pose = Pose::new(info_position, None) * screen_transform;
193 Ui::window_begin(
194 &self.repo.id_window_param,
195 &mut window_pose,
196 Some(Vec2::new(0.4, 0.2)),
197 Some(UiWin::Body),
198 Some(UiMove::None),
199 );
200
201 if Ui::button_img(
202 &self.repo.id_btn_show_hide_param,
203 &self.repo.sprite_hide_param,
204 Some(UiBtnLayout::CenterNoText),
205 None,
206 None,
207 ) {
208 self.repo.show_param = false;
209 }
210 Ui::label("Distance", None, true);
211 Ui::same_line();
212 Ui::label(format!("{:.2}", self.screen_distance), None, true);
213 Ui::same_line();
214 let old_value = self.screen_distance;
215 if let Some(_new_value) = Ui::hslider(
216 &self.repo.id_slider_distance,
217 &mut self.screen_distance,
218 GRAB_X_MARGIN * 2.0,
219 MAX_DISTANCE,
220 None,
221 None,
222 None,
223 None,
224 ) {
225 let max_size = self.screen_distance * PI;
226 let screen_size = self.screen_size;
227 if screen_size.x > max_size || self.screen_size.y > max_size {
228 self.screen_distance = old_value;
229 } else {
230 adapt = true;
231 }
232 }
233
234 Ui::label("Diagonal", None, true);
235 Ui::same_line();
236 Ui::label(format!("{:.2}", self.screen_diagonal), None, true);
237 Ui::same_line();
238 let old_value = self.screen_diagonal;
239 if let Some(new_value) = Ui::hslider(
240 &self.repo.id_slider_size,
241 &mut self.screen_diagonal,
242 MIN_DIAGONAL,
243 MAX_DIAGONAL,
244 None,
245 None,
246 None,
247 None,
248 ) {
249 let max_size = self.screen_distance * PI;
250 let screen_size = self.screen_size * new_value / old_value;
251 if screen_size.x > max_size || self.screen_size.y > max_size {
252 self.screen_diagonal = old_value;
253 } else {
254 self.screen_size = screen_size;
255 adapt = true;
256 }
257 }
258
259 Ui::label("Curvature", None, true);
260 Ui::same_line();
261 Ui::label(format!("{:.2}", self.screen_flattening), None, true);
262 Ui::same_line();
263 if let Some(new_value) = Ui::hslider(
264 &self.repo.id_slider_flattening,
265 &mut self.screen_flattening,
266 0.0,
267 1.0,
268 None,
269 None,
270 None,
271 None,
272 ) {
273 self.screen_flattening = new_value;
274 adapt = true;
275 }
276 } else {
277 let info_position = Vec3::new(
278 0.0, self.screen_size.y / 2.0 + 0.04 * factor_size,
280 bounds.center.z,
281 );
282 let mut window_pose = Pose::new(info_position, None) * screen_transform;
283 Ui::window_begin(&self.repo.id_window_param, &mut window_pose, None, Some(UiWin::Body), Some(UiMove::None));
284 if Ui::button_img(
285 &self.repo.id_btn_show_hide_param,
286 &self.repo.sprite_show_param,
287 Some(UiBtnLayout::CenterNoText),
288 Some(Vec2::new(0.03 * factor_size, 0.03 * factor_size)),
289 None,
290 ) {
291 self.repo.show_param = true;
292 let head = Input::get_head();
293 self.screen_pose.position = head.position;
294 }
295 }
296 Ui::window_end();
297 if adapt {
298 self.adapt_screen();
299 }
300 screen_transform
301 }
302
303 fn sound_position(&self, factor: i8) -> Vec3 {
305 let up = self.screen_pose.get_up();
306 let forward = self.screen_pose.get_forward();
307 let cross = Vec3::cross(up, forward);
308 cross * factor as f32 * self.sound_spacing_factor
309 }
310
311 fn adapt_screen(&mut self) {
312 let distance = self.screen_distance;
313 let flattening = if self.screen_flattening <= 0.0 { 500.0 } else { 1.0 / self.screen_flattening - 1.0 };
314 let radius = distance + flattening;
315
316 let width = self.screen_size.x;
317 let height = self.screen_size.y;
318
319 self.screen = {
320 let mut verts: Vec<Vertex> = vec![];
321 let mut inds: Vec<Inds> = vec![];
322
323 let aspect_ratio = width / height;
324
325 let perimeter = 2.0 * PI * radius;
326
327 let subdiv_v = 30u32;
328 let subdiv_u = (subdiv_v as f32 * aspect_ratio) as u32;
329
330 let angle_v = 2.0 * PI * height / perimeter;
331 let angle_u = 2.0 * PI * width / perimeter;
332 let delta_v = angle_v / subdiv_v as f32;
333 let delta_u = angle_u / subdiv_u as f32;
334
335 for j in 0..subdiv_v {
336 let v = -angle_v / 2.0 + (j as f32 * delta_v) + PI / 2.0;
337 for i in 0..subdiv_u {
338 let u = -angle_u / 2.0 + (i as f32 * delta_u) + PI / 2.0;
339 let x = radius * v.sin() * u.cos();
340 let y = radius * v.cos();
341 let z = radius * v.sin() * u.sin() - flattening;
342
343 verts.push(Vertex::new(
344 Vec3::new(x, y, z), Vec3::FORWARD,
346 Some(Vec2::new(i as f32 / (subdiv_u - 1) as f32, j as f32 / (subdiv_v - 1) as f32)),
347 None,
348 ));
349
350 let nb_row = subdiv_u;
353 let last_line = j == subdiv_v - 1;
354 if !last_line {
355 let row_is_even = i % 2 == 0;
356 let last_row = i == nb_row - 1;
357 let a = j * nb_row + i;
358 let b = j * nb_row + i + 1;
359 let c = (j + 1) * nb_row + i;
360 if row_is_even {
361 if !last_row {
362 inds.push(a);
363 inds.push(b);
364 inds.push(c);
365 inds.push(a);
366 inds.push(c);
367 inds.push(b);
368 }
370 } else {
371 let c_previous = (j + 1) * nb_row + i - 1;
372 let c_following = (j + 1) * nb_row + i + 1;
373 inds.push(a);
374 inds.push(c);
375 inds.push(c_previous);
376 inds.push(a);
377 inds.push(c_previous);
378 inds.push(c);
379 if !last_row {
380 inds.push(a);
381 inds.push(c_following);
382 inds.push(c);
383 inds.push(a);
384 inds.push(c);
385 inds.push(c_following);
386
387 inds.push(a);
388 inds.push(b);
389 inds.push(c_following);
390 inds.push(a);
391 inds.push(c_following);
392 inds.push(b);
393 } else {
395 }
397 }
398 }
399 }
400 }
401
402 let mut mesh = Mesh::new();
403 mesh.set_inds(inds.as_slice());
404 mesh.set_verts(verts.as_slice(), true);
405
406 mesh
407 };
408 }
409}