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