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