9d89c2c4acc6108c7fd64b469d4eba4c7b4d72d5
[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 #[cfg(feature = "rust-rocket")]
9 extern crate rust_rocket;
10
11 use cgmath::{Euler, Matrix4, Rad, SquareMatrix, Vector3, Vector4, Zero};
12 use cgmath::conv::array4x4;
13 use glium::{DisplayBuild, Surface};
14 use glutin::ElementState::Pressed;
15 use glutin::Event::KeyboardInput;
16 use glutin::VirtualKeyCode;
17 use mandelwow_lib::*;
18 use std::f32::consts::PI;
19 use timer::Timer;
20
21 #[cfg(target_os = "emscripten")]
22 use std::os::raw::{c_int, c_void};
23
24 fn gl_info(display : &glium::Display) {
25     if cfg!(feature = "logging") {
26         let version = *display.get_opengl_version();
27         let api = match version {
28             glium::Version(glium::Api::Gl, _, _) => "OpenGL",
29             glium::Version(glium::Api::GlEs, _, _) => "OpenGL ES"
30         };
31         println!("{} context verson: {}", api, display.get_opengl_version_string());
32     }
33 }
34
35 #[cfg(target_os = "emscripten")]
36 #[allow(non_camel_case_types)]
37 type em_callback_func = unsafe extern fn();
38 #[cfg(target_os = "emscripten")]
39 extern {
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) where F : FnMut() -> support::Action {
49     MAIN_LOOP_CALLBACK.with(|log| {
50             *log.borrow_mut() = &callback as *const _ as *mut c_void;
51             });
52
53     unsafe { emscripten_set_main_loop(wrapper::<F>, 0, 1); }
54
55     unsafe extern "C" fn wrapper<F>() where F : FnMut() -> support::Action {
56         MAIN_LOOP_CALLBACK.with(|z| {
57             let closure = *z.borrow_mut() as *mut F;
58             (*closure)();
59         });
60     }
61 }
62
63 #[cfg(not(target_os = "emscripten"))]
64 pub fn set_main_loop_callback<F>(callback : F) where F : FnMut() -> support::Action {
65     support::start_loop(callback);
66 }
67
68 fn main() {
69     let mut soundplayer = sound::start();
70
71     let mut rocket = rust_rocket::Rocket::new().unwrap();
72     rocket.get_track_mut("test");
73     rocket.get_track_mut("test2");
74     rocket.get_track_mut("a:test2");
75     let mut current_row = 0;
76
77     let display = glutin::WindowBuilder::new()
78         .with_dimensions(1280, 720)
79         .with_gl_profile(glutin::GlProfile::Core)
80         //.with_fullscreen(glutin::get_primary_monitor())
81         .with_depth_buffer(24)
82         .with_vsync()
83         .with_srgb(Some(true))
84         .with_title("MandelWow")
85         .build_glium()
86         //.build_glium_debug(glium::debug::DebugCallbackBehavior::PrintAll)
87         .unwrap();
88
89     gl_info(&display);
90
91     let text = text::Text::new(&display);
92     let mandelwow_program = mandelwow::program(&display);
93     let bounding_box_program = bounding_box::solid_fill_program(&display);
94     let shaded_program = shaded_cube::shaded_program(&display);
95
96     let mut timer = Timer::new();
97     let mut camera = support::camera::CameraState::new();
98     let mut bounding_box_enabled = true;
99     let mut fullscreen = true;
100
101     // These are the bounds of the 3D Mandelwow section which we render in 3-space.
102     let bounds = Cube {
103         xmin: -2.0,
104         xmax:  0.7,
105         ymin: -1.0,
106         ymax:  1.0,
107         zmin: -1.1,
108         zmax:  1.1,
109     };
110     let mandelwow_bbox = bounding_box::BoundingBox::new(&display, &bounds, &bounding_box_program);
111     let shaded_cube = ShadedCube::new(&display, &shaded_program);
112
113     const SEA_XSIZE: usize = 24;
114     const SEA_ZSIZE: usize = 20;
115     let sea_xmin = -14.0f32;
116     let sea_xmax =  14.0f32;
117     let sea_y = -2.5;
118     let sea_zmin =  -2.0f32;
119     let sea_zmax = -26.0f32;
120     let sea_xstep = (sea_xmax - sea_xmin) / (SEA_XSIZE as f32);
121     let sea_zstep = (sea_zmax - sea_zmin) / (SEA_ZSIZE as f32);
122
123     let mut sea = [[Vector3::zero(); SEA_ZSIZE]; SEA_XSIZE];
124     for x in 0..SEA_XSIZE {
125         for z in 0..SEA_ZSIZE {
126             sea[x][z] = Vector3 {
127                 x: sea_xmin + (x as f32) * sea_xstep,
128                 y: sea_y,
129                 z: sea_zmin + (z as f32) * sea_zstep,
130             };
131         }
132     }
133
134     let mut last_hit = 0.0f32;
135     let mut hit_time = 0.0f32;
136     set_main_loop_callback(|| {
137         let t = timer.t;
138         let new_hit = sound::hit_event(&mut soundplayer);
139         if new_hit > last_hit {
140             hit_time = t;
141         }
142         last_hit = new_hit;
143         let hit_delta = t - hit_time;
144         let hit_scale = 1. / (1. + hit_delta * hit_delta * 15.0) + 1.;
145
146         camera.update();
147         let perspview = camera.get_perspview();
148
149         // Vary the wow factor to slice the Mandelwow along its 4th dimension.
150         let wmin = -0.8;
151         let wmax =  0.8;
152         let wsize = wmax - wmin;
153         let wow = (((t * 0.7).sin() + 1.0) / 2.0) * wsize + wmin;
154
155         //println!("t={} w={:?} camera={:?}", t, w, camera.get_pos());
156
157         let mut frame = display.draw();
158         frame.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
159
160         let rotation = Matrix4::from(
161             Euler { x: Rad(t.sin() / 3.), y: Rad(t.sin() / 2.), z: Rad(t / 1.5)});
162         let z_trans = -3.0;  // Send the model back a little bit so it fits the screen.
163         let scale =
164             Matrix4::from_diagonal(Vector4::new(hit_scale, hit_scale, hit_scale, 1.0));
165         let model2 =
166             Matrix4::from_translation(Vector3::unit_z() * z_trans) * rotation * scale;
167         let model = array4x4(model2);
168
169         // Draw the bounding box before the fractal, when the Z-buffer is still clear,
170         // so the lines behind the semi-translucent areas will be drawn.
171         if bounding_box_enabled {
172             let uniforms = uniform! {
173                 model: model,
174                 view:  camera.get_view(),
175                 perspective: camera.get_perspective(),
176             };
177             mandelwow_bbox.draw(&mut frame, &uniforms);
178         }
179
180         for x in 0..SEA_XSIZE {
181             for z in 0..SEA_ZSIZE {
182                 let wave = ((x as f32 / SEA_XSIZE as f32 * PI * 5.0 + t * 2.0).sin() +
183                             (z as f32 / SEA_ZSIZE as f32 * PI * 3.0 + t * 3.0).sin()) * 0.3;
184                 let model = Matrix4::from_translation(sea[x][z] + Vector3 {x: 0., y: wave, z: 0.});
185                 let uniforms = uniform! {
186                     model: array4x4(model),
187                     perspview: perspview,
188                     col: [0., (1. - wave).abs() * 0.5,  wave.abs()],
189                 };
190                 shaded_cube.draw(&mut frame, &uniforms);
191             }
192         }
193
194         mandelwow::draw(&display, &mut frame, &mandelwow_program, model, &camera, &bounds, wow);
195
196         text.draw(&mut frame, &perspview);
197
198         frame.finish().unwrap();
199
200         for ev in display.poll_events() {
201             match ev {
202                 glutin::Event::Closed |
203                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::Escape)) |
204                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::Q)) => {
205                     return support::Action::Stop
206                 },
207                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::B)) => {
208                     bounding_box_enabled ^= true;
209                 },
210                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::P)) => {
211                     timer.pause ^= true;
212                 },
213                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::PageUp)) => {
214                     timer.t += 0.01;
215                 },
216                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::PageDown)) => {
217                     timer.t -= 0.01;
218                 },
219                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::F10)) => {
220                     screenshot(&display);
221                 },
222                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::F11)) => {
223                     fullscreen ^= true;
224                     if fullscreen {
225                         // Not implemented on Linux
226                         glutin::WindowBuilder::new()
227                             .with_fullscreen(glutin::get_primary_monitor())
228                             .with_depth_buffer(24)
229                             .rebuild_glium(&display).unwrap();
230                     } else {
231                         glutin::WindowBuilder::new()
232                             .rebuild_glium(&display).unwrap();
233                     }
234                 },
235                 ev => camera.process_input(&ev),
236             }
237         }
238
239         if let Some(event) = rocket.poll_events() {
240             match event {
241                 rust_rocket::Event::SetRow(row) => {
242                     println!("SetRow (row: {:?})", row);
243                     current_row = row;
244                 }
245                 rust_rocket::Event::Pause(_) => {
246                     let track1 = rocket.get_track("test").unwrap();
247                     println!("Pause (value: {:?}) (row: {:?})", track1.get_value(current_row as f32), current_row);
248                 }
249                 _ => (),
250             }
251             println!("{:?}", event);
252         }
253
254         timer.update();
255
256         support::Action::Continue
257     });
258 }