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