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