stereokit_rust/framework/
screen.rs

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
54/// The video stepper
55pub 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
81/// This code may be called in some threads, so no StereoKit code
82impl 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), //
104                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
117/// All the code here run in the main thread
118impl Screen {
119    /// Create the video player
120    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    /// Called from IStepper::step, after check_event here you can draw your UI and scene
146    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    /// Here is managed the screen position, its rotundity, size and distance
155    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, //
168            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, //
173            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, //
279                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    /// Calculate sound position. If factor < 0 this is for left else for right
304    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), //
345                        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                    //Log::diag(format!("vertex: {} {} {}", x, y, z));
351
352                    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                                //Log::diag(format!("inds: a{} b{} c{}", a, b, c));
369                            }
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                                //Log::diag(format!("inds: a{} b{} c{} c-{} c+{} ", a, b, c, c_previous, c_following));
394                            } else {
395                                //Log::diag(format!("inds: a{} c{} c-{} ", a, c, c_previous));
396                            }
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}