Remove deprecatd emscripten option ERROR_ON_MISSING_LIBRARIES
[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 instant::{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 fullscreen = Some(glutin::window::Fullscreen::Borderless(event_loop.primary_monitor()));
257     let window = glutin::window::WindowBuilder::new()
258         //.with_dimensions(1280, 720)
259         //.with_fullscreen(fullscreen);
260         ;
261     //.with_title("MandelWow");
262     let context = glutin::ContextBuilder::new()
263         //.with_gl_profile(glutin::GlProfile::Core)
264         //.with_gl(glutin::GlRequest::Specific(glutin::Api::WebGl, (2, 0)))
265         .with_gl(glutin::GlRequest::Specific(
266             glutin::Api::OpenGlEs,
267             (3, 0),
268         ))
269         //.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (4, 0)))
270         //.with_depth_buffer(24)
271         .with_vsync(true);
272
273     let display = glium::Display::new(window, context, &event_loop).unwrap();
274     gl_info(&display);
275     let mut world = World::new(&display);
276
277     let mut timer = Timer::new();
278     let mut camera = support::camera::CameraState::new();
279     let _fullscreen = true;
280
281     event_loop.run(move |event, _, control_flow| {
282         let t = timer.t;
283         let new_hit = sound::hit_event(&mut soundplayer);
284         if new_hit > world.last_hit {
285             world.hit_time = t;
286         }
287         world.last_hit = new_hit;
288
289         camera.update();
290
291         *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::from_nanos(16666667));
292         match event {
293             Event::NewEvents(cause) => {
294                 match cause {
295                     event::StartCause::ResumeTimeReached { .. } | event::StartCause::Init => {
296                         world.draw_frame(&display, &camera, t);
297                     },
298                     _ => {}
299                 }
300             } _ => (),
301         }
302         if let Event::WindowEvent { event, .. } = event {
303             camera.process_input(&event);
304             match event {
305                 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
306                 WindowEvent::KeyboardInput { input, .. } => {
307                     if input.state == event::ElementState::Pressed {
308                         if let Some(key) = input.virtual_keycode {
309                             match key {
310                                 VirtualKeyCode::Escape | VirtualKeyCode::Q => {
311                                     *control_flow = ControlFlow::Exit;
312                                 }
313                                 _ => (),
314                             }
315                         }
316                     }
317                 }
318                 /*
319                 KeyboardInput { input: glutin::KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::Escape), .. } } |
320                 KeyboardInput { input: glutin::KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::Q), .. } } => {
321                     *control_flow = ControlFlow::Exit;
322                 },
323                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::B) } => {
324                     bounding_box_enabled ^= true;
325                 },
326                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::P) } => {
327                     timer.pause ^= true;
328                 },
329                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::PageUp) } => {
330                     timer.t += 0.01;
331                 },
332                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::PageDown) } => {
333                     timer.t -= 0.01;
334                 },
335                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::F10) } => {
336                     screenshot(&display);
337                 },
338                 KeyboardInput { state: Pressed, virtual_keycode: Some(VirtualKeyCode::F11) } => {
339                     fullscreen ^= true;
340                     if fullscreen {
341                         // Not implemented on Linux
342                         glutin::WindowBuilder::new()
343                             .with_fullscreen(glutin::get_primary_monitor())
344                             .with_depth_buffer(24)
345                             .rebuild_glium(&display).unwrap();
346                     } else {
347                         glutin::WindowBuilder::new()
348                             .rebuild_glium(&display).unwrap();
349                     }
350                 },
351                 */
352                 _ => (),
353             }
354         }
355
356         timer.update();
357     });
358 }