Load a test XM module, does not yet play.
[mandelwow.git] / mandelwow.rs
1 // Wow. Such fractal.
2
3 #[macro_use]
4
5 extern crate glium;
6 extern crate glutin;
7 extern crate libxm;
8
9 use glium::{DisplayBuild, Surface};
10 use glium::index::{IndexBuffer, PrimitiveType};
11 use glutin::ElementState::Pressed;
12 use glutin::Event::KeyboardInput;
13 use glutin::VirtualKeyCode;
14 use libxm::XMContext;
15 use std::fs::File;
16 use std::io::Read;
17
18
19 mod support;
20
21 #[derive(Copy, Clone)]
22 struct Cube {
23     xmin: f32,
24     ymin: f32,
25     zmin: f32,
26     xmax: f32,
27     ymax: f32,
28     zmax: f32,
29 }
30
31 /*
32 fn mand(cx: f32, cy: f32) -> [f32; 3] {
33     let maxiter = 64;
34     let mut iter = maxiter;
35     let mut zx = cx;
36     let mut zy = cy;
37     while iter > 0 {
38         let zx2 = zx * zx;
39         let zy2 = zy * zy;
40         if zx2 + zy2 > 4.0 {
41             return [iter as f32 / maxiter as f32, 1.0, 1.0];
42         }
43         zy = zx * zy * 2.0 + cy;
44         zx = zx2 - zy2 + cx;
45         iter -= 1;
46     }
47
48     [0.0, 0.0, 0.0]
49 }
50 */
51
52 fn mandelwow_program(display: &glium::Display) -> glium::Program {
53     return program!(display,
54         140 => {
55             vertex: r#"
56                 #version 140
57                 uniform mat4 perspective;
58                 uniform mat4 view;
59                 uniform mat4 model;
60                 uniform vec2 z0;
61                 in vec3 position;
62                 out vec2 c;
63                 out vec2 z;
64
65                 void main() {
66                     mat4 modelview = view * model;
67                     gl_Position = perspective * modelview * vec4(position, 1.0);
68                     c = vec2(position.x, position.y);
69                     z = vec2(z0.x, z0.y);
70                 }
71             "#,
72
73             fragment: r#"
74                 #version 140
75                 precision highp float;
76                 in vec2 c;
77                 in vec2 z;
78                 out vec4 f_color;
79
80                 void main() {
81                     float zx = z.x;
82                     float zy = z.y;
83                     int maxiter = 64;
84                     int iter = maxiter;
85                     while (iter > 0) {
86                         float zx2 = zx * zx;
87                         float zy2 = zy * zy;
88                         if (zx2 * zy2 > 4.0) {
89                           float index = 1.0 - float(iter) / float(maxiter);
90                           f_color = vec4(index, index * 0.5, index, index * 0.5);
91                           return;
92                         }
93                         zy = zx * zy * 2.0 + c.y;
94                         zx = zx2 - zy2 + c.x;
95                         iter -= 1;
96                     }
97                     f_color = vec4((sin(z.y) + 1.0) / 2,
98                                    (sin(c.y) + 1.0) / 2,
99                                    (sin(c.x) + 1.0) / 2,
100                                    1.0);
101                 }
102             "#
103         }).unwrap();
104 }
105
106 fn solid_fill_program(display: &glium::Display) -> glium::Program {
107     let vertex_shader_src = r#"
108         #version 140
109         in vec3 position;
110         uniform mat4 perspective;
111         uniform mat4 view;
112         uniform mat4 model;
113
114         void main() {
115             mat4 modelview = view * model;
116             gl_Position = perspective * modelview * vec4(position, 1.0);
117         }
118     "#;
119
120     let fragment_shader_src = r#"
121         #version 140
122
123         out vec4 color;
124
125         void main() {
126             color = vec4(1.0, 1.0, 1.0, 1.0);
127         }
128     "#;
129
130     return glium::Program::from_source(display,
131                                        vertex_shader_src,
132                                        fragment_shader_src,
133                                        None).unwrap();
134 }
135
136 fn bounding_box<U>(display: &glium::Display,
137                    frame: &mut glium::Frame,
138                    program: &glium::Program,
139                    uniforms: &U,
140                    cube: &Cube) where U: glium::uniforms::Uniforms {
141
142     #[derive(Copy, Clone)]
143     struct Vertex { position: [f32; 3] }
144     implement_vertex!(Vertex, position);
145
146     let cube = [
147         Vertex { position: [cube.xmin, cube.ymin, cube.zmin] },
148         Vertex { position: [cube.xmax, cube.ymin, cube.zmin] },
149         Vertex { position: [cube.xmax, cube.ymax, cube.zmin] },
150         Vertex { position: [cube.xmin, cube.ymax, cube.zmin] },
151         Vertex { position: [cube.xmin, cube.ymin, cube.zmax] },
152         Vertex { position: [cube.xmax, cube.ymin, cube.zmax] },
153         Vertex { position: [cube.xmax, cube.ymax, cube.zmax] },
154         Vertex { position: [cube.xmin, cube.ymax, cube.zmax] },
155     ];
156     let vb = glium::VertexBuffer::new(display, &cube).unwrap();
157
158     let params = glium::DrawParameters {
159         depth: glium::Depth {
160             test: glium::draw_parameters::DepthTest::IfLess,
161             write: true,
162             .. Default::default()
163         },
164         blend: glium::Blend::alpha_blending(),
165         .. Default::default()
166     };
167
168     let front_indices = IndexBuffer::new(display, PrimitiveType::LineLoop,
169                                          &[0, 1, 2, 3u16]).unwrap();
170     frame.draw(&vb, &front_indices, program, uniforms, &params).unwrap();
171
172     let back_indices = IndexBuffer::new(display, PrimitiveType::LineLoop,
173                                         &[4, 5, 6, 7u16]).unwrap();
174     frame.draw(&vb, &back_indices, program, uniforms, &params).unwrap();
175
176     let sides_indices = IndexBuffer::new(display, PrimitiveType::LinesList,
177                                          &[0, 4, 1, 5, 2, 6, 3, 7u16]).unwrap();
178     frame.draw(&vb, &sides_indices, program, uniforms, &params).unwrap();
179 }
180
181 fn mandel<U>(display: &glium::Display,
182           frame: &mut glium::Frame,
183           program: &glium::Program,
184           uniforms: &U,
185           bounds: &Cube,
186           z: [f32; 2]) where U: glium::uniforms::Uniforms {
187
188     #[derive(Copy, Clone)]
189     struct Vertex {
190         position: [f32; 3],
191         color: [f32; 3],
192     }
193     implement_vertex!(Vertex, position, color);
194
195     let xmin = bounds.xmin;
196     let xmax = bounds.xmax;
197     let ymin = bounds.ymin;
198     let ymax = bounds.ymax;
199
200     let width = xmax - xmin;
201     let height = ymax - ymin;
202     let xres: usize = 1;
203     let yres: usize = 1;
204     let xstep = width / (xres as f32);
205     let ystep = height / (yres as f32);
206     let vb_size = (xres * 2 + 4) * yres;
207     let mut v : Vec<Vertex> = Vec::with_capacity(vb_size);
208     v.resize(vb_size, Vertex { position: [0.0, 0.0, -1.0], color: [0.0, 0.0, 0.0] });
209     let mut i: usize = 0;
210     let mut vy = ymin;
211     let vz = z[1];
212     for _ in 0..yres {
213         let mut vx = xmin;
214         let c = [0.0, 0.0, 1.0];
215         v[i] = Vertex { position: [vx, vy+ystep, vz], color: c }; i += 1;
216         v[i] = Vertex { position: [vx, vy,       vz], color: c }; i += 1;
217         for _ in 0..xres {
218             //let c = mand(vx, vy);
219             v[i] = Vertex { position: [vx+xstep, vy+ystep, vz], color: c }; i += 1;
220             v[i] = Vertex { position: [vx+xstep, vy,       vz], color: c }; i += 1;
221             vx += xstep;
222         }
223         v[i] = Vertex { position: [vx,   vy, vz], color: c }; i += 1;
224         v[i] = Vertex { position: [xmin, vy, vz], color: c }; i += 1;
225         vy += ystep;
226     }
227
228     //let vb = glium::VertexBuffer::empty_persistent(display, width*height*3).unwrap();
229     let vb = glium::VertexBuffer::new(display, &v).unwrap();
230
231     let indices = glium::index::NoIndices(glium::index::PrimitiveType::TriangleStrip);
232     //let indices = glium::index::NoIndices(glium::index::PrimitiveType::LineStrip);
233     //let indices = glium::IndexBuffer::new(display, PrimitiveType::TrianglesList,
234     //                                      &[0u16, 1, 2]).unwrap();
235
236     let params = glium::DrawParameters {
237         depth: glium::Depth {
238             test: glium::draw_parameters::DepthTest::IfLess,
239             write: true,
240             .. Default::default()
241         },
242         blend: glium::Blend::alpha_blending(),
243         .. Default::default()
244     };
245
246     frame.draw(&vb, &indices, program, uniforms, &params).unwrap();
247 }
248
249 fn mandelwow(display: &glium::Display,
250              mut frame: &mut glium::Frame,
251              program: &glium::Program,
252              model: [[f32; 4]; 4],
253              camera: &support::camera::CameraState,
254              bounds: &Cube,
255              mandel_w: f32) {
256     let mut z0 = [mandel_w, 0f32];
257     let zres = 50;
258     let zmin = bounds.zmin;
259     let zmax = bounds.zmax;
260     let zstep = (zmax - zmin) / zres as f32;
261     let mut zy = zmin;
262     for _ in 0..zres {
263         z0[1] = zy;
264         zy += zstep;
265
266         let uniforms = uniform! {
267             z0: z0,
268             model: model,
269             view:  camera.get_view(),
270             perspective: camera.get_perspective(),
271         };
272
273         mandel(&display, &mut frame, &program, &uniforms, bounds, z0);
274     }
275 }
276 fn play_xm(raw_xm: &[u8]) {
277     let freq = 48000u32;
278     let mut xm = XMContext::new(&raw_xm, freq).unwrap();
279 }
280
281 fn main() {
282     let mut xm = Vec::new();
283     File::open("flora.xm").unwrap().read_to_end(&mut xm).unwrap();
284     play_xm(&xm);
285
286     let display = glium::glutin::WindowBuilder::new()
287         //.with_dimensions(1024, 768)
288         .with_fullscreen(glutin::get_primary_monitor())
289         .with_depth_buffer(24)
290         .with_vsync()
291         .with_title(format!("MandelWow"))
292         .build_glium()
293         .unwrap();
294
295     let program = mandelwow_program(&display);
296     let bounding_box_program = solid_fill_program(&display);
297
298     let mut camera = support::camera::CameraState::new();
299     let mut t: f32 = 0.0;
300     let mut pause = false;
301     let mut bounding_box_enabled = true;
302     let mut fullscreen = true;
303
304     support::start_loop(|| {
305         camera.update();
306
307         if !pause {
308             // Increment time
309             t += 0.01;
310         }
311
312         // These are the bounds of the 3D Mandelwow section which we render in 3-space.
313         let bounds = Cube {
314             xmin: -2.0,
315             xmax:  0.7,
316             ymin: -1.0,
317             ymax:  1.0,
318             zmin: -1.2,
319             zmax:  1.2,
320         };
321
322         // Vary the wow factor to slice the Mandelwow along its 4th dimension.
323         let wmin = -0.8;
324         let wmax =  0.8;
325         let wsize = wmax - wmin;
326         let wow = (((t * 0.7).sin() + 1.0) / 2.0) * wsize + wmin;
327
328         //println!("t={} w={:?} camera={:?}", t, w, camera.get_pos());
329
330         let mut frame = display.draw();
331         frame.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
332
333         let z_trans = -2.0;  // Send the model back a little bit so it fits the screen.
334         let model = [
335             [ t.cos(),  t.sin(),  0.0,     0.0],
336             [-t.sin(),  t.cos(),  0.0,     0.0],
337             [     0.0,  0.0,      1.0,     0.0],
338             [     0.0,  0.0,      z_trans, 1.0f32]
339         ];
340
341         // Draw the bounding box before the fractal, when the Z-buffer is still clear, so the lines
342         // behind the semi-translucent areas will be drawn.
343         if bounding_box_enabled {
344             let uniforms = uniform! {
345                 model: model,
346                 view:  camera.get_view(),
347                 perspective: camera.get_perspective(),
348             };
349             bounding_box(&display, &mut frame, &bounding_box_program, &uniforms, &bounds);
350         }
351
352         mandelwow(&display, &mut frame, &program, model, &camera, &bounds, wow);
353         frame.finish().unwrap();
354
355         for ev in display.poll_events() {
356             match ev {
357                 glium::glutin::Event::Closed |
358                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::Escape)) |
359                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::Q)) => {
360                     return support::Action::Stop
361                 },
362                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::B)) => {
363                     bounding_box_enabled ^= true;
364                 },
365                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::F)) => {
366                     fullscreen ^= true;
367                     if fullscreen {
368                         glutin::WindowBuilder::new()
369                             .with_fullscreen(glutin::get_primary_monitor())
370                             .rebuild_glium(&display).unwrap();
371                     } else {
372                         glutin::WindowBuilder::new()
373                             .rebuild_glium(&display).unwrap();
374                     }
375                 },
376                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::P)) => {
377                     pause ^= true;
378                 },
379                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::PageUp)) => {
380                     t += 0.01;
381                 },
382                 KeyboardInput(Pressed, _, Some(VirtualKeyCode::PageDown)) => {
383                     t -= 0.01;
384                 },
385                 ev => camera.process_input(&ev),
386             }
387         }
388
389         support::Action::Continue
390     });
391
392 }