Improve event handling
[mandelwow.git] / main.rs
1 use cgmath::conv::array4x4;
2 use cgmath::{Euler, Matrix4, Rad, SquareMatrix, Vector3, Vector4, Zero};
3 use glium::glutin::event::{ self, Event, VirtualKeyCode, WindowEvent };
4 use glium::glutin::event_loop::ControlFlow;
5 use glium::{Display, Program, Surface, uniform};
6 use instant::Duration;
7 use mandelwow_lib::*;
8 use std::f32::consts::PI;
9 use std::rc::Rc;
10
11 #[cfg(target_os = "emscripten")]
12 use std::os::raw::{c_int, c_void};
13
14 fn gl_info(display: &glium::Display) {
15     if cfg!(feature = "logging") {
16         let version = *display.get_opengl_version();
17         let api = match version {
18             glium::Version(glium::Api::Gl, _, _) => "OpenGL",
19             glium::Version(glium::Api::GlEs, _, _) => "OpenGL ES",
20         };
21         println!(
22             "{} context verson: {}",
23             api,
24             display.get_opengl_version_string()
25         );
26     }
27 }
28
29 const SEA_XSIZE: usize = 40;
30 const SEA_ZSIZE: usize = 25;
31
32 struct World {
33     mandelwow_program: Rc<Program>,
34     mandelwow_bounds: Cube,
35     mandelwow_bbox: BoundingBox,
36     bounding_box_enabled: bool,
37
38     shaded_cube: ShadedCube,
39     text: Text,
40
41     sea: [[Vector3<f32>; SEA_ZSIZE]; SEA_XSIZE],
42
43     // For the zoom animation synchronized to the drum-hits
44     hit_time: f32,
45     last_hit: f32,
46 }
47
48 impl World {
49     pub fn new(display: &glium::Display) -> World {
50         let mandelwow_program = Rc::new(mandelwow::program(display));
51         let bounding_box_program = Rc::new(bounding_box::solid_fill_program(display));
52         let shaded_program = Rc::new(shaded_cube::shaded_program(display));
53
54         // These are the bounds for the 3D slice of the 4D Mandelwow
55         let mandelwow_bounds = Cube {
56             xmin: -2.0,
57             xmax: 0.7,
58             ymin: -1.0,
59             ymax: 1.0,
60             zmin: -1.1,
61             zmax: 1.1,
62         };
63
64         // Generate a wavy sea made of cubes
65         let sea_xmin = -20.0f32;
66         let sea_xmax = 20.0f32;
67         let sea_y = -2.5;
68         let sea_zmin = -2.0f32;
69         let sea_zmax = -27.0f32;
70         let sea_xstep = (sea_xmax - sea_xmin) / (SEA_XSIZE as f32);
71         let sea_zstep = (sea_zmax - sea_zmin) / (SEA_ZSIZE as f32);
72         println!("xstep={} ystep={:?}", sea_xstep, sea_zstep);
73
74         let mut sea = [[Vector3::zero(); SEA_ZSIZE]; SEA_XSIZE];
75         #[allow(clippy::needless_range_loop)]
76         for x in 0..SEA_XSIZE {
77             for z in 0..SEA_ZSIZE {
78                 sea[x][z] = Vector3 {
79                     x: sea_xmin + (x as f32) * sea_xstep,
80                     y: sea_y,
81                     z: sea_zmin + (z as f32) * sea_zstep,
82                 };
83             }
84         }
85
86         World {
87             mandelwow_program,
88             mandelwow_bbox: BoundingBox::new(
89                 display, &mandelwow_bounds, bounding_box_program),
90             mandelwow_bounds,
91             bounding_box_enabled: true,
92
93             shaded_cube: ShadedCube::new(display, shaded_program),
94             text: text::Text::new(display),
95             sea,
96
97             hit_time: 0.0,
98             last_hit: 0.0,
99         }
100     }
101
102     fn draw_frame(
103         &self,
104         display: &Display,
105         camera: &support::camera::CameraState,
106         t: f32,
107     ) {
108         let perspview = camera.get_perspview();
109
110         let hit_delta = t - self.hit_time;
111         let hit_scale = 1. / (1. + hit_delta * hit_delta * 15.0) + 1.;
112
113         // Vary the wow factor to slice the Mandelwow along its 4th dimension.
114         let wmin = -0.8;
115         let wmax = 0.8;
116         let wsize = wmax - wmin;
117         let wow = (((t * 0.7).sin() + 1.0) / 2.0) * wsize + wmin;
118
119         //println!("t={} w={:?} camera={:?}", t, w, camera.get_pos());
120
121         let mut frame = display.draw();
122         frame.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
123
124         let rotation = Matrix4::from(Euler {
125             x: Rad(t.sin() / 3.),
126             y: Rad(t.sin() / 2.),
127             z: Rad(t / 1.5),
128         });
129         let z_trans = -3.0; // Send the model back a little bit so it fits the screen.
130         let scale = Matrix4::from_diagonal(Vector4::new(hit_scale, hit_scale, hit_scale, 1.0));
131         let model2 = Matrix4::from_translation(Vector3::unit_z() * z_trans) * rotation * scale;
132         let model = array4x4(model2);
133
134         // Draw the bounding box before the fractal, when the Z-buffer is still clear,
135         // so the lines behind the semi-translucent areas will be drawn.
136         if self.bounding_box_enabled {
137             let uniforms = uniform! {
138                 model: model,
139                 view:  camera.get_view(),
140                 perspective: camera.get_perspective(),
141             };
142             self.mandelwow_bbox.draw(&mut frame, &uniforms);
143         }
144
145         let text_rot = Matrix4::from_angle_x(cgmath::Deg(-90.0f32));
146         let text_pos = Matrix4::from_translation(Vector3 {
147             x: 0.0,
148             y: 0.501,
149             z: 0.0f32,
150         }) * text_rot;
151         for x in 0..SEA_XSIZE {
152             for z in 0..SEA_ZSIZE {
153                 let wave = ((x as f32 / SEA_XSIZE as f32 * PI * 5.0 + t * 2.0).sin()
154                     + (z as f32 / SEA_ZSIZE as f32 * PI * 3.0 + t * 3.0).sin())
155                     * 0.3;
156                 let model = Matrix4::from_translation(
157                     self.sea[x][z]
158                         + Vector3 {
159                             x: 0.,
160                             y: wave,
161                             z: 0.,
162                         },
163                 );
164                 let uniforms = uniform! {
165                     model: array4x4(model),
166                     perspview: perspview,
167                     col: [0., (1. - wave).abs() * 0.5,  wave.abs()],
168                 };
169                 self.shaded_cube.draw(&mut frame, &uniforms);
170                 let model = model * text_pos;
171                 let c = (x + z * SEA_XSIZE) as u8 as char;
172                 self.text.draw(&mut frame, c, &model, &perspview);
173             }
174         }
175
176         mandelwow::draw(
177             &display,
178             &mut frame,
179             &self.mandelwow_program,
180             model,
181             &camera,
182             &self.mandelwow_bounds,
183             wow,
184         );
185
186         frame.finish().unwrap();
187     }
188 }
189
190
191 #[cfg(target_os = "emscripten")]
192 #[allow(non_camel_case_types)]
193 type em_callback_func = unsafe extern "C" fn();
194 #[cfg(target_os = "emscripten")]
195 extern "C" {
196     fn emscripten_set_main_loop(func: em_callback_func, fps: c_int, simulate_infinite_loop: c_int);
197 }
198
199 #[cfg(target_os = "emscripten")]
200 thread_local!(static MAIN_LOOP_CALLBACK: std::cell::RefCell<*mut c_void> =
201               std::cell::RefCell::new(std::ptr::null_mut()));
202
203 #[cfg(target_os = "emscripten")]
204 pub fn set_main_loop_callback<F>(callback: F)
205 where
206     F: FnMut() -> support::Action,
207 {
208     MAIN_LOOP_CALLBACK.with(|log| {
209         *log.borrow_mut() = &callback as *const _ as *mut c_void;
210     });
211
212     unsafe {
213         emscripten_set_main_loop(wrapper::<F>, 0, 1);
214     }
215
216     unsafe extern "C" fn wrapper<F>()
217     where
218         F: FnMut() -> support::Action,
219     {
220         MAIN_LOOP_CALLBACK.with(|z| {
221             let closure = *z.borrow_mut() as *mut F;
222             (*closure)();
223         });
224     }
225 }
226
227 #[cfg(not(target_os = "emscripten"))]
228 pub fn set_main_loop_callback<F>(callback: F)
229 where
230     F: FnMut() -> support::Action,
231 {
232     support::start_loop(callback);
233 }
234
235 //extern crate gleam;
236
237 /*
238 extern "C" {
239     fn emscripten_GetProcAddress(
240         name: *const ::std::os::raw::c_char,
241     ) -> *const ::std::os::raw::c_void;
242 }
243 */
244
245 fn main() {
246     /*
247     let gl = gleam::gl::GlesFns::load_with(|addr| {
248             let addr = std::ffi::CString::new(addr).unwrap();
249             emscripten_GetProcAddress(addr.into_raw() as *const _) as *const _
250         });
251     gl.glGetInternalformativ(0, 0, 0, 0, 0);
252     */
253
254     let mut soundplayer = sound::start();
255
256     let event_loop = glutin::event_loop::EventLoop::new();
257     let window = glutin::window::WindowBuilder::new()
258         //.with_dimensions(1280, 720)
259         .with_title("MandelWow");
260     let context = glutin::ContextBuilder::new()
261         //.with_gl_profile(glutin::GlProfile::Core)
262         //.with_gl(glutin::GlRequest::Specific(glutin::Api::WebGl, (2, 0)))
263         .with_gl(glutin::GlRequest::Specific(
264             glutin::Api::OpenGlEs,
265             (3, 0),
266         ))
267         //.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (4, 0)))
268         //.with_depth_buffer(24)
269         .with_vsync(true);
270
271     let display = glium::Display::new(window, context, &event_loop).unwrap();
272     gl_info(&display);
273     let mut world = World::new(&display);
274
275     let mut timer = Timer::new();
276     let mut camera = support::camera::CameraState::new();
277     let mut fullscreen = false;
278
279     event_loop.run(move |event, _, control_flow| {
280         timer.update();
281         let t = timer.t;
282         let new_hit = sound::hit_event(&mut soundplayer);
283         if new_hit > world.last_hit {
284             world.hit_time = t;
285         }
286         world.last_hit = new_hit;
287
288         camera.update();
289
290         *control_flow = ControlFlow::WaitUntil(timer.now + Duration::from_nanos(16_666_667));
291         match event {
292             Event::MainEventsCleared => {
293                 world.draw_frame(&display, &camera, t);
294             }
295             Event::NewEvents(cause) => {
296                 match cause {
297                     event::StartCause::ResumeTimeReached { .. } | event::StartCause::Init => {
298                         // FIXME
299                     },
300                     _ => {}
301                 }
302             }
303             Event::WindowEvent { event, .. } => {
304                 camera.process_input(&event);
305                 match event {
306                     WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
307                     WindowEvent::KeyboardInput { input, .. } => {
308                         if input.state == event::ElementState::Pressed {
309                             if let Some(key) = input.virtual_keycode {
310                                 match key {
311                                     VirtualKeyCode::Escape | VirtualKeyCode::Q => {
312                                         *control_flow = ControlFlow::Exit;
313                                     }
314                                     VirtualKeyCode::B => world.bounding_box_enabled ^= true,
315                                     VirtualKeyCode::P => timer.pause ^= true,
316                                     VirtualKeyCode::PageUp => timer.t += 0.1,
317                                     VirtualKeyCode::PageDown => timer.t -= 0.2,
318                                     VirtualKeyCode::F10 => screenshot::take_screenshot(&display),
319                                     VirtualKeyCode::F | VirtualKeyCode::F11 | VirtualKeyCode::Return => {
320                                         fullscreen ^= true;
321                                         let fs = if fullscreen {
322                                             // let monitor_handle = display.gl_window().window()
323                                             //    .available_monitors().next().unwrap();
324                                             Some(glium::glutin::window::Fullscreen::Borderless(None))
325                                         } else {
326                                             None
327                                         };
328                                         display.gl_window().window().set_fullscreen(fs);
329                                     }
330                                     _ => (),
331                                 }
332                             }
333                         }
334                     }
335                     _ => (),
336                 }
337             },
338             _ => (),
339         }
340     });
341 }