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