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