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