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