]> git.tdb.fi Git - libs/gl.git/blob - demos/desertpillars.cpp
Use HDR rendering and a colorcurve postprocessor in desertpillars
[libs/gl.git] / demos / desertpillars.cpp
1 #include <cmath>
2 #include <cstdlib>
3 #include <msp/core/application.h>
4 #include <msp/core/getopt.h>
5 #include <msp/gl/animatedobject.h>
6 #include <msp/gl/bloom.h>
7 #include <msp/gl/box.h>
8 #include <msp/gl/colorcurve.h>
9 #include <msp/gl/cylinder.h>
10 #include <msp/gl/environmentmap.h>
11 #include <msp/gl/framebuffer.h>
12 #include <msp/gl/grid.h>
13 #include <msp/gl/instancescene.h>
14 #include <msp/gl/light.h>
15 #include <msp/gl/lighting.h>
16 #include <msp/gl/basicmaterial.h>
17 #include <msp/gl/mesh.h>
18 #include <msp/gl/meshbuilder.h>
19 #include <msp/gl/object.h>
20 #include <msp/gl/pipeline.h>
21 #include <msp/gl/program.h>
22 #include <msp/gl/programbuilder.h>
23 #include <msp/gl/renderer.h>
24 #include <msp/gl/shader.h>
25 #include <msp/gl/shadowmap.h>
26 #include <msp/gl/simplescene.h>
27 #include <msp/gl/technique.h>
28 #include <msp/gl/tests.h>
29 #include <msp/gl/texturecube.h>
30 #include <msp/gl/windowview.h>
31 #include <msp/graphics/simplewindow.h>
32 #include <msp/input/keyboard.h>
33 #include <msp/input/keys.h>
34 #include <msp/time/timestamp.h>
35 #include <msp/time/timedelta.h>
36 #include <msp/time/utils.h>
37
38 using namespace std;
39 using namespace Msp;
40
41 /**
42 This application demonstrates a variety of features of the mspgl library,
43 including:
44 - Creating meshes from multiple parts
45 - Creating a mesh and then modifying it
46 - Shadow mapping
47 - Environment mapped reflections
48 - Skybox using a cube map texture
49 - Effects with nested pipelines
50 - Complex multitexturing
51 - Shader-based deformations
52 - Creating a normalmapped texture through rendering
53
54 To run the program in fullscreen mode, specify --fullscreen on the command
55 line.
56
57 During execution the following keys are available:
58 esc    exit
59 space  stop camera movement
60 s      stop cube rotation
61 f      freeze cube shape
62 */
63 class DesertPillars: public RegisteredApplication<DesertPillars>
64 {
65 private:
66         struct Options
67         {
68                 Graphics::WindowOptions window_opts;
69
70                 Options(const Graphics::Display &, int, char **);
71         };
72
73         struct ObjectData
74         {
75                 GL::Mesh *mesh;
76                 GL::Object *object;
77
78                 ObjectData();
79                 ~ObjectData();
80         };
81
82         class Cube: public GL::AnimatedObject
83         {
84         private:
85                 GL::ProgramData shdata;
86
87         public:
88                 Cube(const GL::Object &);
89
90                 void set_spherify(float);
91
92                 virtual void setup_render(GL::Renderer &, const GL::Tag &) const;
93         };
94
95         Msp::Graphics::Display display;
96         Options options;
97         Msp::Graphics::Window window;
98         Msp::Graphics::GLContext gl_context;
99         Msp::Input::Keyboard keyboard;
100
101         GL::Program skybox_shprog;
102         GL::Technique skybox_tech;
103         GL::TextureCube skybox_tex;
104         ObjectData skybox_data;
105
106         GL::Program shadow_shprog;
107
108         GL::Program ground_shprog;
109         GL::ProgramData ground_shdata;
110         GL::Texture2D tiles_texture;
111         GL::Texture2D tiles_normalmap;
112         GL::Texture2D sand_texture;
113         GL::Texture2D sand_normalmap;
114         GL::Technique ground_tech;
115         ObjectData ground_data;
116
117         GL::BasicMaterial pillar_material;
118         GL::Technique pillar_tech;
119         std::vector<ObjectData> pillar_data;
120         std::vector<GL::AnimatedObject *> pillars;
121
122         GL::Program cube_shprog;
123         GL::Program cube_shadow_shprog;
124         GL::BasicMaterial cube_material;
125         GL::Technique cube_tech;
126         ObjectData cube_data;
127         Cube *cube;
128         GL::EnvironmentMap *env_cube;
129
130         GL::WindowView view;
131         GL::Pipeline pipeline;
132         GL::Camera camera;
133         GL::SimpleScene sky_scene;
134         GL::InstanceScene scene;
135         GL::Lighting lighting;
136         GL::Light light;
137         GL::ShadowMap shadow_scene;
138         GL::Bloom bloom;
139         GL::ColorCurve colorcurve;
140
141         GL::Pipeline env_pipeline;
142
143         Time::TimeStamp last_tick;
144         float camera_angle;
145         bool camera_stopped;
146         float cube_angle;
147         bool cube_stopped;
148         unsigned cube_shape;
149         float cube_phase;
150         bool cube_frozen;
151
152         static const char texture_src[];
153         static const char skybox_src[];
154         static const char ground_src[];
155         static const char cube_src[];
156         static const char cube_shadow_src_tail[];
157         static const float cube_shapes[];
158
159 public:
160         DesertPillars(int, char **);
161         ~DesertPillars();
162
163 private:
164         void setup_view();
165         void create_pipeline();
166         void create_skybox();
167         static void create_skybox_face(GL::TextureCube &, GL::TextureCubeFace);
168         void create_tiles_texture();
169         void create_sand_texture();
170         static void gaussian_blur(unsigned char *, unsigned, unsigned);
171         static void create_normalmap(const unsigned char *, unsigned char *, unsigned, unsigned, float);
172         void create_ground();
173         static float ground_height(float, float);
174         void create_pillars();
175         void create_cube();
176         static void create_cube_face(GL::MeshBuilder &, const GL::Vector3 &, const GL::Vector3 &, const GL::Vector3 &, unsigned);
177
178 public:
179         virtual int main();
180 private:
181         virtual void tick();
182
183         void key_press(unsigned);
184 };
185
186 const char DesertPillars::texture_src[] =
187         "import msp_interface;\n"
188         "#pragma MSP stage(vertex)\n"
189         "void main()\n"
190         "{\n"
191         "       gl_Position = vec4(vertex.xy*2.0-1.0, -vertex.z*2.0, 1.0);\n"
192         "       passthrough;\n"
193         "}\n"
194         "#pragma MSP stage(fragment)\n"
195         "layout(location=1) out vec4 frag_normal;\n"
196         "void main()\n"
197         "{\n"
198         "       frag_color = color;\n"
199         "       frag_normal = vec4(normal*0.5+0.5, 1.0);\n"
200         "}\n";
201
202 const char DesertPillars::skybox_src[] =
203         "import msp_interface;\n"
204         "uniform samplerCube sky;\n"
205         "#pragma MSP stage(vertex)\n"
206         "void main()\n"
207         "{\n"
208         "       gl_Position = projection_matrix*vec4(mat3(eye_obj_matrix)*vertex.xyz, 1.0);\n"
209         "       passthrough;\n"
210         "}\n"
211         "#pragma MSP stage(fragment)\n"
212         "void main()\n"
213         "{\n"
214         "       frag_color = texture(sky, vertex.xyz);\n"
215         "}\n";
216
217 const char DesertPillars::ground_src[] =
218         "import phong;\n"
219         "uniform sampler2D texture1;\n"
220         "uniform sampler2D normalmap1;\n"
221         "uniform sampler2D texture2;\n"
222         "uniform sampler2D normalmap2;\n"
223         "const bool use_normal_map = true;\n"
224         "const bool use_shadow_map = true;\n"
225         "#pragma MSP stage(vertex)\n"
226         "layout(location=7) in float ground_type;\n"
227         "#pragma MSP stage(fragment)\n"
228         "vec4 get_diffuse_color()\n"
229         "{\n"
230         "       return mix(texture(texture1, texcoord.xy*3.0), texture(texture2, texcoord.xy), ground_type);\n"
231         "}\n"
232         "vec4 get_normal_sample()\n"
233         "{\n"
234         "       return mix(texture(normalmap1, texcoord.xy*3.0).rgb, texture(normalmap2, texcoord.xy).rgb, ground_type);\n"
235         "}\n";
236
237 const char DesertPillars::cube_src[] =
238         "import phong;\n"
239         "const bool use_specular = true;\n"
240         "const bool use_reflectivity = true;\n"
241         "uniform float spherify;\n"
242         "#pragma MSP stage(vertex)\n"
243         "layout(location=7) in vec3 sphere_coord;\n"
244         "vec4 transform_position(vec4 pos)\n"
245         "{\n"
246         "       return eye_obj_matrix*vec4(mix(vertex.xyz, sphere_coord, spherify), 1.0);\n"
247         "}\n"
248         "vec3 transform_normal(vec3 pos)\n"
249         "{\n"
250         "       return eye_obj_normal_matrix*normalize(mix(normal, normalize(sphere_coord), spherify));\n"
251         "}\n";
252
253 const char DesertPillars::cube_shadow_src_tail[] =
254         "#pragma MSP stage(fragment)\n"
255         "void main()\n"
256         "{\n"
257         "       frag_color = vec4(1.0);\n"
258         "}\n";
259
260 const float DesertPillars::cube_shapes[] = { -0.4, 0.5, 1.0, 0.3 };
261
262
263 DesertPillars::Options::Options(const Graphics::Display &dpy, int argc, char **argv)
264 {
265         GetOpt getopt;
266         getopt.add_option('f', "fullscreen", window_opts.fullscreen, GetOpt::NO_ARG).set_help("Run in fullscreen mode");
267         getopt(argc, argv);
268
269         if(window_opts.fullscreen)
270         {
271                 const Graphics::VideoMode &mode = dpy.get_desktop_mode();
272                 window_opts.width = mode.width;
273                 window_opts.height = mode.height;
274         }
275         else
276         {
277                 window_opts.width = 800;
278                 window_opts.height = 600;
279         }
280 }
281
282
283 DesertPillars::DesertPillars(int argc, char **argv):
284         options(display, argc, argv),
285         window(display, options.window_opts),
286         gl_context(window),
287         keyboard(window),
288         skybox_shprog(skybox_src),
289         shadow_shprog("occluder.glsl"),
290         ground_shprog(ground_src),
291         cube_shprog(cube_src),
292         cube_shadow_shprog(string(cube_src)+cube_shadow_src_tail),
293         view(window, gl_context),
294         pipeline(view),
295         shadow_scene(2048, scene, light),
296         bloom(window.get_width(), window.get_height()),
297         env_pipeline(512, 512),
298         camera_angle(0),
299         camera_stopped(false),
300         cube_angle(0),
301         cube_stopped(false),
302         cube_shape(0),
303         cube_phase(0),
304         cube_frozen(false)
305 {
306         window.set_title("Desert Pillars");
307         window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &DesertPillars::exit), 0));
308         if(options.window_opts.fullscreen)
309                 window.show_cursor(false);
310         keyboard.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &DesertPillars::key_press), false));
311
312         setup_view();
313         create_pipeline();
314         create_skybox();
315         create_ground();
316         create_pillars();
317         create_cube();
318 }
319
320 DesertPillars::~DesertPillars()
321 {
322         delete env_cube;
323         delete cube;
324         for(vector<GL::AnimatedObject *>::iterator i=pillars.begin(); i!=pillars.end(); ++i)
325                 delete *i;
326 }
327
328 void DesertPillars::setup_view()
329 {
330         camera.set_aspect_ratio(float(window.get_width())/window.get_height());
331         camera.set_up_direction(GL::Vector3(0, 0, 1));
332         camera.set_depth_clip(1, 50);
333         view.set_camera(&camera);
334         view.set_content(&pipeline);
335 }
336
337 void DesertPillars::create_pipeline()
338 {
339         pipeline.set_multisample(8);
340         pipeline.set_hdr(true);
341
342         /* The shadow map is focused on the part of the scene that contains the
343         pillars and the cube.  Making the ground cast shadows as well would result
344         either in a very low spatial resolution of the shadow map, or ugly artifacts
345         as the ground crosses the shadow map boundary. */
346         shadow_scene.set_target(GL::Vector3(0, 0, 0), 10);
347         shadow_scene.set_darkness(1);
348
349         // Put the sun pretty high in the sky
350         light.set_diffuse(GL::Color(2.0));
351         light.set_position(GL::Vector4(0.5, -2, 3, 0));
352         lighting.attach(0, light);
353         lighting.set_ambient(GL::Color(0.2));
354
355         // The skybox is rendered first
356         pipeline.add_pass(0, sky_scene);
357
358         GL::Pipeline::Pass *pass = &pipeline.add_pass(0, shadow_scene);
359         pass->set_lighting(&lighting);
360         pass->set_depth_test(&GL::DepthTest::lequal());
361
362         /* A bloom filter enhances the realism of bright surfaces, even if there
363         isn't anything really glowy in the scene. */
364         bloom.set_strength(0.3);
365         pipeline.add_postprocessor(bloom);
366
367         /* Lighting calculations are best done in linear color space, so the final
368         image must be converted to srgb for display. */
369         colorcurve.set_srgb();
370         pipeline.add_postprocessor(colorcurve);
371
372         /* Initialize a second pipeline to render the environment map.  It has the
373         same renderables and passes, but no postprocessors or camera. */
374         env_pipeline.add_pass(0, sky_scene);
375         pass = &env_pipeline.add_pass(0, shadow_scene);
376         pass->set_lighting(&lighting);
377         pass->set_depth_test(&GL::DepthTest::lequal());
378 }
379
380 void DesertPillars::create_skybox()
381 {
382         skybox_tex.storage(GL::SRGB, 128);
383         skybox_tex.set_min_filter(GL::LINEAR);
384         skybox_tex.set_wrap(GL::CLAMP_TO_EDGE);
385         for(unsigned i=0; i<6; ++i)
386                 create_skybox_face(skybox_tex, skybox_tex.enumerate_faces(i));
387
388         GL::RenderPass &pass = skybox_tech.add_pass(0);
389         pass.set_shader_program(&skybox_shprog, 0);
390         pass.set_texture(0, &skybox_tex);
391
392         // The shader will use the vertex coordinates to initialize texture coordinates as well
393         skybox_data.mesh = new GL::Mesh(GL::VERTEX3);
394         GL::BoxBuilder(10, 10, 10).build(*skybox_data.mesh);
395         skybox_data.object = new GL::Object(skybox_data.mesh, &skybox_tech);
396
397         sky_scene.add(*skybox_data.object);
398 }
399
400 void DesertPillars::create_skybox_face(GL::TextureCube &texture, GL::TextureCubeFace face)
401 {
402         unsigned char *pixels = new unsigned char[128*128*3];
403         for(int y=0; y<128; ++y)
404                 for(int x=0; x<128; ++x)
405                 {
406                         unsigned i = (x+y*128)*3;
407                         GL::Vector3 v = texture.get_texel_direction(face, x, y);
408                         if(v.z>0)
409                         {
410                                 float l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
411                                 // Render a sky-like gradient, with deeper blue at the zenith
412                                 pixels[i] = 96-48*v.z/l;
413                                 pixels[i+1] = 168-84*v.z/l;
414                                 pixels[i+2] = 255;
415                         }
416                         else
417                         {
418                                 // Fill with a desert-y color below horizon
419                                 pixels[i] = 240;
420                                 pixels[i+1] = 224;
421                                 pixels[i+2] = 160;
422                         }
423                 }
424         texture.image(face, 0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
425         delete[] pixels;
426 }
427
428 void DesertPillars::create_tiles_texture()
429 {
430         unsigned width = 256;
431         unsigned height = 256;
432         tiles_texture.storage(GL::RGB, width, height);
433         tiles_texture.set_min_filter(GL::LINEAR);
434         tiles_normalmap.storage(GL::RGB, width, height);
435         tiles_normalmap.set_min_filter(GL::LINEAR);
436
437         GL::Mesh tiles((GL::VERTEX3, GL::NORMAL3, GL::COLOR4_UBYTE));
438
439         // Prepare some lookup tables for rendering the tiles
440         float split = 1.0f/3;
441         float spacing = split*0.02f;
442         float bevel = split*0.1f;
443         float coords[] = { 0.0f, spacing, spacing+bevel, split-bevel, split, split+spacing, split+spacing+bevel, 1.0f-bevel, 1.0f };
444         unsigned order[] = { 4, 1, 3, 2, 1, 1, 2, 2,
445                 1, 4, 2, 3, 4, 4, 3, 3,
446                 1, 1, 2, 2, 1, 4, 2, 3,
447                 4, 4, 3, 3, 4, 1, 3, 2,
448                 2, 3, 2, 2, 3, 3, 3, 2 };
449
450         GL::MeshBuilder bld(tiles);
451
452         // Create a dark background
453         bld.color(0.2f, 0.2f, 0.2f);
454         bld.normal(0.0f, 0.0f, 1.0f);
455         bld.begin(GL::TRIANGLE_STRIP);
456         bld.vertex(0.0f, 1.0f);
457         bld.vertex(0.0f, 0.0f);
458         bld.vertex(1.0f, 1.0f);
459         bld.vertex(1.0f, 0.0f);
460         bld.end();
461
462         // Create the four tiles
463         bld.color(GL::Color(0.95f, 0.8f, 0.65f).to_linear());
464         for(unsigned i=0; i<2; ++i)
465                 for(unsigned j=0; j<2; ++j)
466                 {
467                         for(unsigned k=0; k<4; ++k)
468                         {
469                                 bld.begin(GL::TRIANGLE_STRIP);
470                                 float facing = (k%2)*2-1.0f;
471                                 if(k<2)
472                                         bld.normal(0.0f, 0.7071f*facing, 0.7071f);
473                                 else
474                                         bld.normal(0.7071f*facing, 0.0f, 0.7071f);
475                                 for(unsigned l=0; l<4; ++l)
476                                         bld.vertex(coords[i*4+order[k*8+l*2]], coords[j*4+order[k*8+l*2+1]], (l%2 ? bevel : 0.0f));
477                                 bld.end();
478                         }
479
480                         bld.begin(GL::TRIANGLE_STRIP);
481                         bld.normal(0.0f, 0.0f, 1.0f);
482                         for(unsigned l=0; l<4; ++l)
483                                 bld.vertex(coords[i*4+order[32+l*2]], coords[j*4+order[32+l*2+1]], bevel);
484                         bld.end();
485                 }
486
487         GL::Program shprog(texture_src);
488
489         // Use an FBO to turn the geometry into a normalmapped texture
490         GL::Framebuffer fbo;
491         fbo.attach(GL::COLOR_ATTACHMENT0, tiles_texture);
492         fbo.attach(GL::COLOR_ATTACHMENT1, tiles_normalmap);
493         GL::Bind bind_fbo(fbo);
494         GL::Renderer renderer;
495         renderer.set_shader_program(&shprog, 0);
496         tiles.draw(renderer);
497 }
498
499 void DesertPillars::create_sand_texture()
500 {
501         unsigned width = 512;
502         unsigned height = 512;
503
504         sand_texture.storage(GL::SRGB, width/16, height/16);
505         sand_texture.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
506         sand_texture.set_max_anisotropy(4);
507         sand_texture.set_auto_generate_mipmap(true);
508         sand_normalmap.storage(GL::RGB, width, height);
509         sand_normalmap.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
510         sand_normalmap.set_max_anisotropy(4);
511         sand_normalmap.set_auto_generate_mipmap(true);
512
513         unsigned char *pixels = new unsigned char[width*height*3];
514         unsigned char *bump = new unsigned char[width*height];
515         for(unsigned y=0; y<height; ++y)
516                 for(unsigned x=0; x<width; ++x)
517                 {
518                         unsigned i = (x+y*width)*3;
519                         unsigned c = rand()%16;
520                         pixels[i] = 224+c;
521                         pixels[i+1] = 208+c;
522                         pixels[i+2] = 160;
523                         bump[x+y*width] = rand();
524                 }
525         sand_texture.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
526         gaussian_blur(bump, width, height);
527         create_normalmap(bump, pixels, width, height, 4);
528         sand_normalmap.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
529         delete[] pixels;
530         delete[] bump;
531 }
532
533 void DesertPillars::gaussian_blur(unsigned char *data, unsigned width, unsigned height)
534 {
535         /* Create a gaussian blur kernel for Ïƒ=2.  Gaussian blur is separable, so a
536         1-dimensional kernel is enough. */
537         float kernel[9];
538         float sum = 0;
539         for(int i=-4; i<=4; ++i)
540                 sum += (kernel[4+i] = exp(-i*i*0.5));
541         for(unsigned i=0; i<9; ++i)
542                 kernel[i] /= sum;
543
544         unsigned char *line = new unsigned char[max(width, height)];
545         // Perform the blur in the X direction
546         for(unsigned y=0; y<height; ++y)
547         {
548                 for(unsigned x=0; x<width; ++x)
549                 {
550                         float value = 0;
551                         for(int i=-4; i<=4; ++i)
552                                 value += data[(x+width+i)%width+y*width]*kernel[4+i];
553                         line[x] = value;
554                 }
555                 copy(line, line+width, data+y*width);
556         }
557         // And then in the Y direction
558         for(unsigned x=0; x<width; ++x)
559         {
560                 for(unsigned y=0; y<height; ++y)
561                 {
562                         float value = 0;
563                         for(int i=-4; i<=4; ++i)
564                                 value += data[x+((y+height+i)%height)*width]*kernel[4+i];
565                         line[y] = value;
566                 }
567                 for(unsigned y=0; y<height; ++y)
568                         data[x+y*width] = line[y];
569         }
570         delete[] line;
571 }
572
573 void DesertPillars::create_normalmap(const unsigned char *bump, unsigned char *normals, unsigned width, unsigned height, float depth)
574 {
575         for(unsigned y=0; y<height; ++y)
576                 for(unsigned x=0; x<width; ++x)
577                 {
578                         float dz_x = (bump[(x+1)%width+y*width]-bump[(x+width-1)%width+y*width])*depth/510;
579                         float dz_y = (bump[x+((y+1)%height)*width]-bump[x+((y+height-1)%height)*width])*depth/510;
580                         float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
581                         unsigned i = (x+y*width)*3;
582                         normals[i] = (0.5-0.5*dz_x/l)*255;
583                         normals[i+1] = (0.5-0.5*dz_y/l)*255;
584                         normals[i+2] = (0.5+0.5/l)*255;
585                 }
586 }
587
588 void DesertPillars::create_ground()
589 {
590         create_tiles_texture();
591         create_sand_texture();
592
593         ground_shdata.uniform("texture1", 0);
594         ground_shdata.uniform("normalmap1", 1);
595         ground_shdata.uniform("texture2", 2);
596         ground_shdata.uniform("normalmap2", 3);
597
598         GL::RenderPass *pass = &ground_tech.add_pass(0);
599         pass->set_shader_program(&ground_shprog, &ground_shdata);
600         pass->set_texture(0, &tiles_texture);
601         pass->set_texture(1, &tiles_normalmap);
602         pass->set_texture(2, &sand_texture);
603         pass->set_texture(3, &sand_normalmap);
604
605         /* No shadow pass here; the ground only receives shadows, but doesn't cast
606         them. */
607
608         GL::VertexFormat vfmt = (GL::VERTEX3, GL::NORMAL3, GL::TANGENT3, GL::BINORMAL3, GL::TEXCOORD2, GL::ATTRIB1,7);
609         ground_data.mesh = new GL::Mesh(vfmt);
610
611         // Create a base grid
612         GL::GridBuilder(80, 80, 200, 200).tbn().build(*ground_data.mesh);
613
614         // And modify it with a heightmap
615         unsigned n_vertices = ground_data.mesh->get_n_vertices();
616         unsigned pos = vfmt.offset(GL::VERTEX3);
617         unsigned nor = vfmt.offset(GL::NORMAL3);
618         unsigned tan = vfmt.offset(GL::TANGENT3);
619         unsigned bin = vfmt.offset(GL::BINORMAL3);
620         unsigned tex = vfmt.offset(GL::TEXCOORD2);
621         unsigned gt = vfmt.offset(GL::make_indexed_component(GL::ATTRIB1, 7));
622         for(unsigned i=0; i<n_vertices; ++i)
623         {
624                 float *v = ground_data.mesh->modify_vertex(i);
625                 v[pos+2] = ground_height(v[pos], v[pos+1]);
626
627                 float dz_x = (ground_height(v[pos]+0.01, v[pos+1])-ground_height(v[pos]-0.01, v[pos+1]))/0.02;
628                 float dz_y = (ground_height(v[pos], v[pos+1]+0.01)-ground_height(v[pos], v[pos+1]-0.01))/0.02;
629                 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
630                 v[nor] = -dz_x/l;
631                 v[nor+1] = -dz_y/l;
632                 v[nor+2] = 1/l;
633
634                 l = sqrt(dz_x*dz_x+1);
635                 v[tan] = 1/l;
636                 v[tan+2] = dz_x/l;
637
638                 l = sqrt(dz_y*dz_y+1);
639                 v[bin+1] = 1/l;
640                 v[bin+2] = dz_y/l;
641
642                 v[gt] = min(v[pos+2]*100, 1.0f);
643
644                 v[tex] *= 20;
645                 v[tex+1] *= 20;
646         }
647         ground_data.object = new GL::Object(ground_data.mesh, &ground_tech);
648
649         scene.add(*ground_data.object);
650 }
651
652 float DesertPillars::ground_height(float x, float y)
653 {
654         // Leave a flat area in the middle
655         float d = sqrt(x*x+y*y);
656         if(d<=6.0f)
657                 return 0;
658
659         // This results in concentric rings of low hills
660         int i = (d-6)/(M_PI*2);
661         float a = atan2(y, x);
662         a *= i*2+5;
663         a += M_PI*(i%3)/2;
664         float h = (i%2) ? 0.5 : 0.3;
665         return (d-6)*0.001f+(1-cos(d-6))*(1-cos(a))*h;
666 }
667
668 void DesertPillars::create_pillars()
669 {
670         // The pillars are a matt off-white
671         pillar_material.set_diffuse(GL::Color(0.9, 0.89, 0.85).to_linear());
672         pillar_material.set_receive_shadows(true);
673
674         GL::RenderPass *pass = &pillar_tech.add_pass(0);
675         pass->set_material(&pillar_material);
676
677         pass = &pillar_tech.add_pass("shadow");
678         pass->set_shader_program(&shadow_shprog, 0);
679
680         pillar_data.reserve(7);
681         for(unsigned i=3; i<=20; ++i)
682         {
683                 unsigned height = 2;
684                 for(unsigned j=2; j<i; ++j)
685                         if(i%j==0)
686                                 ++height;
687
688                 if(pillar_data.size()<=height)
689                         pillar_data.resize(height+1);
690
691                 ObjectData &pd = pillar_data[height];
692                 if(!pd.object)
693                 {
694                         pd.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3));
695                         GL::MeshBuilder bld(*pd.mesh);
696
697                         // Produce a fluted cylinder
698                         unsigned n_flutes = 12;
699                         float r_bottom = cos(M_PI/n_flutes)*0.4;
700                         float flute_depth = (0.4-r_bottom)*2;
701                         float half_w = sin(M_PI/n_flutes)*0.4;
702                         for(unsigned j=0; j<n_flutes; ++j)
703                         {
704                                 float a = j*M_PI*2/n_flutes;
705                                 bld.begin(GL::TRIANGLE_STRIP);
706                                 for(int k=-3; k<=3; k+=2)
707                                 {
708                                         float t = k/3.0f;
709                                         float x = cos(a)*(r_bottom-(1-t*t)*flute_depth)-sin(a)*t*half_w;
710                                         float y = sin(a)*(r_bottom-(1-t*t)*flute_depth)+cos(a)*t*half_w;
711                                         float d = -t*2*flute_depth;
712                                         float l = sqrt(d*d+1);
713                                         bld.normal(cos(a)/l-sin(a)*d/l, sin(a)/l+cos(a)*d/l, 0);
714                                         bld.vertex(x, y, 0.6+height);
715                                         bld.vertex(x, y, 0.6);
716                                 }
717                                 bld.end();
718                         }
719
720                         // Create a square plinth and capitel
721                         bld.set_matrix(GL::Matrix::translation(0, 0, 0.3));
722                         GL::BoxBuilder(1.0, 1.0, 0.6).build(bld);
723                         bld.set_matrix(GL::Matrix::translation(0, 0, height+0.8));
724                         GL::BoxBuilder(1.0, 1.0, 0.4).build(bld);
725
726                         pd.object = new GL::Object(pd.mesh, &pillar_tech);
727                 }
728
729                 GL::AnimatedObject *pillar = new GL::AnimatedObject(*pd.object);
730                 GL::Matrix matrix;
731                 float a = (i-3)*2*M_PI/18;
732                 matrix.translate(cos(a)*5, sin(a)*5, 0);
733                 matrix.rotate(a, 0, 0, 1);
734                 pillar->set_matrix(matrix);
735
736                 pillars.push_back(pillar);
737                 scene.add(*pillar);
738         }
739 }
740
741 void DesertPillars::create_cube()
742 {
743         /* The cube is bluish-gray, with a hard specular reflection to produce a
744         sun-like spot */
745         cube_material.set_diffuse(GL::Color(0.5, 0.5, 0.55).to_linear());
746         cube_material.set_specular(GL::Color(1.0));
747         cube_material.set_shininess(120);
748         cube_material.set_reflectivity(0.5);
749
750         GL::RenderPass *pass = &cube_tech.add_pass(0);
751         pass->set_material(&cube_material);
752         pass->set_shader_program(&cube_shprog, 0);
753
754         pass = &cube_tech.add_pass("shadow");
755         pass->set_shader_program(&cube_shadow_shprog, 0);
756
757         cube_data.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3, GL::ATTRIB3,7));
758         GL::MeshBuilder bld(*cube_data.mesh);
759         create_cube_face(bld, GL::Vector3(-1, -1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 2, 0), 16);
760         bld.offset(cube_data.mesh->get_n_vertices());
761         create_cube_face(bld, GL::Vector3(-1, 1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 0, 2), 16);
762         bld.offset(cube_data.mesh->get_n_vertices());
763         create_cube_face(bld, GL::Vector3(-1, 1, 1), GL::Vector3(2, 0, 0), GL::Vector3(0, -2, 0), 16);
764         bld.offset(cube_data.mesh->get_n_vertices());
765         create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(-2, 0, 0), GL::Vector3(0, 0, 2), 16);
766         bld.offset(cube_data.mesh->get_n_vertices());
767         create_cube_face(bld, GL::Vector3(-1, -1, 1), GL::Vector3(0, 0, -2), GL::Vector3(0, 2, 0), 16);
768         bld.offset(cube_data.mesh->get_n_vertices());
769         create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(0, 0, 2), GL::Vector3(0, 2, 0), 16);
770         cube_data.object = new GL::Object(cube_data.mesh, &cube_tech);
771
772         cube = new Cube(*cube_data.object);
773         env_cube = new GL::EnvironmentMap(512, *cube, env_pipeline);
774         scene.add(*env_cube);
775 }
776
777 void DesertPillars::create_cube_face(GL::MeshBuilder &bld, const GL::Vector3 &base, const GL::Vector3 &side1, const GL::Vector3 &side2, unsigned div)
778 {
779         /* The sides follow the cube map convention where the cross product points
780         inwards.  Since the normal has to point outwards, reverse the order. */
781         GL::Vector3 n;
782         n.x = side2.y*side1.z-side2.z*side1.y;
783         n.y = side2.z*side1.x-side2.x*side1.z;
784         n.z = side2.x*side1.y-side2.y*side1.x;
785         float l = sqrt(n.x*n.x+n.y*n.y+n.z*n.z);
786         bld.normal(n.x/l, n.y/l, n.z/l);
787
788         // Create vertices, with precomputed spherified coordinates
789         for(unsigned i=0; i<=div; ++i)
790                 for(unsigned j=0; j<=div; ++j)
791                 {
792                         GL::Vector3 v;
793                         v.x = base.x+side1.x*i/div+side2.x*j/div;
794                         v.y = base.y+side1.y*i/div+side2.y*j/div;
795                         v.z = base.z+side1.z*i/div+side2.z*j/div;
796
797                         l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
798                         l /= 1.732;
799                         bld.attrib(7, v.x/l, v.y/l, v.z/l);
800
801                         bld.vertex(v);
802                 }
803
804         for(unsigned i=0; i<div; ++i)
805         {
806                 bld.begin(GL::TRIANGLE_STRIP);
807                 for(unsigned j=0; j<=div; ++j)
808                 {
809                         bld.element(i*(div+1)+j);
810                         bld.element((i+1)*(div+1)+j);
811                 }
812                 bld.end();
813         }
814 }
815
816 int DesertPillars::main()
817 {
818         window.show();
819         return Application::main();
820 }
821
822 void DesertPillars::tick()
823 {
824         Time::TimeStamp t = Time::now();
825         Time::TimeDelta dt;
826         if(last_tick)
827                 dt = t-last_tick;
828         last_tick = t;
829
830         if(!camera_stopped)
831         {
832                 camera_angle += (dt/Time::sec)*M_PI*2/30;
833                 if(camera_angle>M_PI*4)
834                         camera_angle -= M_PI*4;
835                 float h = 3+(1-cos(camera_angle*1.5))*3;
836                 float r = sqrt(225-h*h);
837                 camera.set_position(GL::Vector3(cos(camera_angle)*r, sin(camera_angle)*r, 1.5+h));
838                 camera.look_at(GL::Vector3(0, 0, 2));
839         }
840
841         if(!cube_stopped)
842         {
843                 cube_angle += (dt/Time::sec)*M_PI*2/20;
844                 GL::Matrix cube_matrix;
845                 cube_matrix.translate(0, 0, 2.5);
846                 cube_matrix.rotate(cube_angle, 0, 0, 1);
847                 cube_matrix.rotate(cube_angle*0.5, 1, 0, 0);
848                 cube->set_matrix(cube_matrix);
849         }
850
851         if(!cube_frozen)
852         {
853                 cube_phase += (dt/Time::sec)/5;
854                 if(cube_phase>1)
855                 {
856                         cube_phase -= 1;
857                         ++cube_shape;
858                         if(cube_shape>=4)
859                                 cube_shape -= 4;
860                 }
861                 if(cube_phase<0.2)
862                 {
863                         float x = cube_phase*5;
864                         x = (3-2*x)*x*x;
865                         cube->set_spherify((1-x)*cube_shapes[(cube_shape+3)%4]+x*cube_shapes[cube_shape]);
866                 }
867                 else
868                         cube->set_spherify(cube_shapes[cube_shape]);
869         }
870
871         display.tick();
872         view.render();
873 }
874
875 void DesertPillars::key_press(unsigned key)
876 {
877         if(key==Input::KEY_ESC)
878                 exit(0);
879         else if(key==Input::KEY_SPACE)
880                 camera_stopped = !camera_stopped;
881         else if(key==Input::KEY_F)
882                 cube_frozen = !cube_frozen;
883         else if(key==Input::KEY_S)
884                 cube_stopped = !cube_stopped;
885 }
886
887
888 DesertPillars::ObjectData::ObjectData():
889         mesh(0),
890         object(0)
891 { }
892
893 DesertPillars::ObjectData::~ObjectData()
894 {
895         delete object;
896         delete mesh;
897 }
898
899
900 DesertPillars::Cube::Cube(const GL::Object &obj):
901         GL::AnimatedObject(obj)
902 { }
903
904 void DesertPillars::Cube::set_spherify(float s)
905 {
906         shdata.uniform("spherify", s);
907 }
908
909 void DesertPillars::Cube::setup_render(GL::Renderer &renderer, const GL::Tag &tag) const
910 {
911         AnimatedObject::setup_render(renderer, tag);
912         renderer.add_shader_data(shdata);
913 }