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/cylinder.h>
9 #include <msp/gl/environmentmap.h>
10 #include <msp/gl/framebuffer.h>
11 #include <msp/gl/grid.h>
12 #include <msp/gl/instancescene.h>
13 #include <msp/gl/light.h>
14 #include <msp/gl/lighting.h>
15 #include <msp/gl/basicmaterial.h>
16 #include <msp/gl/mesh.h>
17 #include <msp/gl/meshbuilder.h>
18 #include <msp/gl/object.h>
19 #include <msp/gl/pipeline.h>
20 #include <msp/gl/program.h>
21 #include <msp/gl/programbuilder.h>
22 #include <msp/gl/renderer.h>
23 #include <msp/gl/shader.h>
24 #include <msp/gl/shadowmap.h>
25 #include <msp/gl/simplescene.h>
26 #include <msp/gl/technique.h>
27 #include <msp/gl/tests.h>
28 #include <msp/gl/texturecube.h>
29 #include <msp/gl/windowview.h>
30 #include <msp/graphics/simplewindow.h>
31 #include <msp/input/keyboard.h>
32 #include <msp/input/keys.h>
33 #include <msp/time/timestamp.h>
34 #include <msp/time/timedelta.h>
35 #include <msp/time/utils.h>
41 This application demonstrates a variety of features of the mspgl library,
43 - Creating meshes from multiple parts
44 - Creating a mesh and then modifying it
46 - Environment mapped reflections
47 - Skybox using a cube map texture
48 - Effects with nested pipelines
49 - Complex multitexturing
50 - Shader-based deformations
51 - Creating a normalmapped texture through rendering
53 To run the program in fullscreen mode, specify --fullscreen on the command
56 During execution the following keys are available:
58 space stop camera movement
62 class DesertPillars: public RegisteredApplication<DesertPillars>
67 Graphics::WindowOptions window_opts;
69 Options(const Graphics::Display &, int, char **);
81 class Cube: public GL::AnimatedObject
84 GL::ProgramData shdata;
87 Cube(const GL::Object &);
89 void set_spherify(float);
91 virtual void setup_render(GL::Renderer &, const GL::Tag &) const;
94 Msp::Graphics::Display display;
96 Msp::Graphics::Window window;
97 Msp::Graphics::GLContext gl_context;
98 Msp::Input::Keyboard keyboard;
100 GL::Program skybox_shprog;
101 GL::Technique skybox_tech;
102 GL::TextureCube skybox_tex;
103 ObjectData skybox_data;
105 GL::Program shadow_shprog;
107 GL::Program ground_shprog;
108 GL::ProgramData ground_shdata;
109 GL::Texture2D tiles_texture;
110 GL::Texture2D tiles_normalmap;
111 GL::Texture2D sand_texture;
112 GL::Texture2D sand_normalmap;
113 GL::Technique ground_tech;
114 ObjectData ground_data;
116 GL::BasicMaterial pillar_material;
117 GL::Technique pillar_tech;
118 std::vector<ObjectData> pillar_data;
119 std::vector<GL::AnimatedObject *> pillars;
121 GL::Program cube_shprog;
122 GL::Program cube_shadow_shprog;
123 GL::BasicMaterial cube_material;
124 GL::Technique cube_tech;
125 ObjectData cube_data;
127 GL::EnvironmentMap *env_cube;
130 GL::Pipeline pipeline;
132 GL::SimpleScene sky_scene;
133 GL::InstanceScene scene;
134 GL::Lighting lighting;
136 GL::ShadowMap shadow_scene;
139 GL::Pipeline env_pipeline;
141 Time::TimeStamp last_tick;
150 static const char texture_src[];
151 static const char skybox_src[];
152 static const char ground_src[];
153 static const char cube_src[];
154 static const char cube_shadow_src_tail[];
155 static const float cube_shapes[];
158 DesertPillars(int, char **);
163 void create_pipeline();
164 void create_skybox();
165 static void create_skybox_face(GL::TextureCube &, GL::TextureCubeFace);
166 void create_tiles_texture();
167 void create_sand_texture();
168 static void gaussian_blur(unsigned char *, unsigned, unsigned);
169 static void create_normalmap(const unsigned char *, unsigned char *, unsigned, unsigned, float);
170 void create_ground();
171 static float ground_height(float, float);
172 void create_pillars();
174 static void create_cube_face(GL::MeshBuilder &, const GL::Vector3 &, const GL::Vector3 &, const GL::Vector3 &, unsigned);
181 void key_press(unsigned);
184 const char DesertPillars::texture_src[] =
185 "import msp_interface;\n"
186 "#pragma MSP stage(vertex)\n"
189 " gl_Position = vec4(vertex.xy*2.0-1.0, -vertex.z*2.0, 1.0);\n"
192 "#pragma MSP stage(fragment)\n"
193 "layout(location=1) out vec4 frag_normal;\n"
196 " frag_color = color;\n"
197 " frag_normal = vec4(normal*0.5+0.5, 1.0);\n"
200 const char DesertPillars::skybox_src[] =
201 "import msp_interface;\n"
202 "uniform samplerCube sky;\n"
203 "#pragma MSP stage(vertex)\n"
206 " gl_Position = projection_matrix*vec4(mat3(eye_obj_matrix)*vertex.xyz, 1.0);\n"
209 "#pragma MSP stage(fragment)\n"
212 " frag_color = texture(sky, vertex.xyz);\n"
215 const char DesertPillars::ground_src[] =
217 "uniform sampler2D texture1;\n"
218 "uniform sampler2D normalmap1;\n"
219 "uniform sampler2D texture2;\n"
220 "uniform sampler2D normalmap2;\n"
221 "const bool use_normal_map = true;\n"
222 "const bool use_shadow_map = true;\n"
223 "#pragma MSP stage(vertex)\n"
224 "layout(location=7) in float ground_type;\n"
225 "#pragma MSP stage(fragment)\n"
226 "vec4 get_diffuse_color()\n"
228 " return mix(texture(texture1, texcoord.xy*3.0), texture(texture2, texcoord.xy), ground_type);\n"
230 "vec4 get_normal_sample()\n"
232 " return mix(texture(normalmap1, texcoord.xy*3.0).rgb, texture(normalmap2, texcoord.xy).rgb, ground_type);\n"
235 const char DesertPillars::cube_src[] =
237 "const bool use_specular = true;\n"
238 "const bool use_reflectivity = true;\n"
239 "uniform float spherify;\n"
240 "#pragma MSP stage(vertex)\n"
241 "layout(location=7) in vec3 sphere_coord;\n"
242 "vec4 transform_position(vec4 pos)\n"
244 " return eye_obj_matrix*vec4(mix(vertex.xyz, sphere_coord, spherify), 1.0);\n"
246 "vec3 transform_normal(vec3 pos)\n"
248 " return eye_obj_normal_matrix*normalize(mix(normal, normalize(sphere_coord), spherify));\n"
251 const char DesertPillars::cube_shadow_src_tail[] =
252 "#pragma MSP stage(fragment)\n"
255 " frag_color = vec4(1.0);\n"
258 const float DesertPillars::cube_shapes[] = { -0.4, 0.5, 1.0, 0.3 };
261 DesertPillars::Options::Options(const Graphics::Display &dpy, int argc, char **argv)
264 getopt.add_option('f', "fullscreen", window_opts.fullscreen, GetOpt::NO_ARG).set_help("Run in fullscreen mode");
267 if(window_opts.fullscreen)
269 const Graphics::VideoMode &mode = dpy.get_desktop_mode();
270 window_opts.width = mode.width;
271 window_opts.height = mode.height;
275 window_opts.width = 800;
276 window_opts.height = 600;
281 DesertPillars::DesertPillars(int argc, char **argv):
282 options(display, argc, argv),
283 window(display, options.window_opts),
286 skybox_shprog(skybox_src),
287 shadow_shprog("occluder.glsl"),
288 ground_shprog(ground_src),
289 cube_shprog(cube_src),
290 cube_shadow_shprog(string(cube_src)+cube_shadow_src_tail),
291 view(window, gl_context),
293 shadow_scene(2048, scene, light),
294 bloom(window.get_width(), window.get_height()),
295 env_pipeline(512, 512),
297 camera_stopped(false),
304 window.set_title("Desert Pillars");
305 window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &DesertPillars::exit), 0));
306 if(options.window_opts.fullscreen)
307 window.show_cursor(false);
308 keyboard.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &DesertPillars::key_press), false));
318 DesertPillars::~DesertPillars()
322 for(vector<GL::AnimatedObject *>::iterator i=pillars.begin(); i!=pillars.end(); ++i)
326 void DesertPillars::setup_view()
328 camera.set_aspect_ratio(float(window.get_width())/window.get_height());
329 camera.set_up_direction(GL::Vector3(0, 0, 1));
330 camera.set_depth_clip(1, 50);
331 view.set_camera(&camera);
332 view.set_content(&pipeline);
335 void DesertPillars::create_pipeline()
337 pipeline.set_multisample(8);
339 /* The shadow map is focused on the part of the scene that contains the
340 pillars and the cube. Making the ground cast shadows as well would result
341 either in a very low spatial resolution of the shadow map, or ugly artifacts
342 as the ground crosses the shadow map boundary. */
343 shadow_scene.set_target(GL::Vector3(0, 0, 0), 10);
345 // Put the sun pretty high in the sky
346 light.set_position(GL::Vector4(0.5, -2, 3, 0));
347 lighting.attach(0, light);
348 lighting.set_ambient(GL::Color(0.5));
350 // The skybox is rendered first
351 pipeline.add_pass(0, sky_scene);
353 GL::Pipeline::Pass *pass = &pipeline.add_pass(0, shadow_scene);
354 pass->set_lighting(&lighting);
355 pass->set_depth_test(&GL::DepthTest::lequal());
357 /* A bloom filter enhances the realism of bright surfaces, even if there
358 isn't anything really glowy in the scene. */
359 bloom.set_strength(0.3);
360 pipeline.add_postprocessor(bloom);
362 /* Initialize a second pipeline to render the environment map. It has the
363 same renderables and passes, but no postprocessors or camera. */
364 env_pipeline.add_pass(0, sky_scene);
365 pass = &env_pipeline.add_pass(0, shadow_scene);
366 pass->set_lighting(&lighting);
367 pass->set_depth_test(&GL::DepthTest::lequal());
370 void DesertPillars::create_skybox()
372 skybox_tex.storage(GL::RGB, 128);
373 skybox_tex.set_min_filter(GL::LINEAR);
374 skybox_tex.set_wrap(GL::CLAMP_TO_EDGE);
375 for(unsigned i=0; i<6; ++i)
376 create_skybox_face(skybox_tex, skybox_tex.enumerate_faces(i));
378 GL::RenderPass &pass = skybox_tech.add_pass(0);
379 pass.set_shader_program(&skybox_shprog, 0);
380 pass.set_texture(0, &skybox_tex);
382 // The shader will use the vertex coordinates to initialize texture coordinates as well
383 skybox_data.mesh = new GL::Mesh(GL::VERTEX3);
384 GL::BoxBuilder(10, 10, 10).build(*skybox_data.mesh);
385 skybox_data.object = new GL::Object(skybox_data.mesh, &skybox_tech);
387 sky_scene.add(*skybox_data.object);
390 void DesertPillars::create_skybox_face(GL::TextureCube &texture, GL::TextureCubeFace face)
392 unsigned char *pixels = new unsigned char[128*128*3];
393 for(int y=0; y<128; ++y)
394 for(int x=0; x<128; ++x)
396 unsigned i = (x+y*128)*3;
397 GL::Vector3 v = texture.get_texel_direction(face, x, y);
400 float l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
401 // Render a sky-like gradient, with deeper blue at the zenith
402 pixels[i] = 96-48*v.z/l;
403 pixels[i+1] = 168-84*v.z/l;
408 // Fill with a desert-y color below horizon
414 texture.image(face, 0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
418 void DesertPillars::create_tiles_texture()
420 unsigned width = 256;
421 unsigned height = 256;
422 tiles_texture.storage(GL::RGB, width, height);
423 tiles_texture.set_min_filter(GL::LINEAR);
424 tiles_normalmap.storage(GL::RGB, width, height);
425 tiles_normalmap.set_min_filter(GL::LINEAR);
427 GL::Mesh tiles((GL::VERTEX3, GL::NORMAL3, GL::COLOR4_UBYTE));
429 // Prepare some lookup tables for rendering the tiles
430 float split = 1.0f/3;
431 float spacing = split*0.02f;
432 float bevel = split*0.1f;
433 float coords[] = { 0.0f, spacing, spacing+bevel, split-bevel, split, split+spacing, split+spacing+bevel, 1.0f-bevel, 1.0f };
434 unsigned order[] = { 4, 1, 3, 2, 1, 1, 2, 2,
435 1, 4, 2, 3, 4, 4, 3, 3,
436 1, 1, 2, 2, 1, 4, 2, 3,
437 4, 4, 3, 3, 4, 1, 3, 2,
438 2, 3, 2, 2, 3, 3, 3, 2 };
440 GL::MeshBuilder bld(tiles);
442 // Create a dark background
443 bld.color(0.2f, 0.2f, 0.2f);
444 bld.normal(0.0f, 0.0f, 1.0f);
445 bld.begin(GL::TRIANGLE_STRIP);
446 bld.vertex(0.0f, 1.0f);
447 bld.vertex(0.0f, 0.0f);
448 bld.vertex(1.0f, 1.0f);
449 bld.vertex(1.0f, 0.0f);
452 // Create the four tiles
453 bld.color(0.95f, 0.8f, 0.65f);
454 for(unsigned i=0; i<2; ++i)
455 for(unsigned j=0; j<2; ++j)
457 for(unsigned k=0; k<4; ++k)
459 bld.begin(GL::TRIANGLE_STRIP);
460 float facing = (k%2)*2-1.0f;
462 bld.normal(0.0f, 0.7071f*facing, 0.7071f);
464 bld.normal(0.7071f*facing, 0.0f, 0.7071f);
465 for(unsigned l=0; l<4; ++l)
466 bld.vertex(coords[i*4+order[k*8+l*2]], coords[j*4+order[k*8+l*2+1]], (l%2 ? bevel : 0.0f));
470 bld.begin(GL::TRIANGLE_STRIP);
471 bld.normal(0.0f, 0.0f, 1.0f);
472 for(unsigned l=0; l<4; ++l)
473 bld.vertex(coords[i*4+order[32+l*2]], coords[j*4+order[32+l*2+1]], bevel);
477 GL::Program shprog(texture_src);
479 // Use an FBO to turn the geometry into a normalmapped texture
481 fbo.attach(GL::COLOR_ATTACHMENT0, tiles_texture);
482 fbo.attach(GL::COLOR_ATTACHMENT1, tiles_normalmap);
483 GL::Bind bind_fbo(fbo);
484 GL::Renderer renderer;
485 renderer.set_shader_program(&shprog, 0);
486 tiles.draw(renderer);
489 void DesertPillars::create_sand_texture()
491 unsigned width = 512;
492 unsigned height = 512;
494 sand_texture.storage(GL::RGB, width/16, height/16);
495 sand_texture.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
496 sand_texture.set_max_anisotropy(4);
497 sand_texture.set_auto_generate_mipmap(true);
498 sand_normalmap.storage(GL::RGB, width, height);
499 sand_normalmap.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
500 sand_normalmap.set_max_anisotropy(4);
501 sand_normalmap.set_auto_generate_mipmap(true);
503 unsigned char *pixels = new unsigned char[width*height*3];
504 unsigned char *bump = new unsigned char[width*height];
505 for(unsigned y=0; y<height; ++y)
506 for(unsigned x=0; x<width; ++x)
508 unsigned i = (x+y*width)*3;
509 unsigned c = rand()%16;
513 bump[x+y*width] = rand();
515 sand_texture.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
516 gaussian_blur(bump, width, height);
517 create_normalmap(bump, pixels, width, height, 4);
518 sand_normalmap.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
523 void DesertPillars::gaussian_blur(unsigned char *data, unsigned width, unsigned height)
525 /* Create a gaussian blur kernel for σ=2. Gaussian blur is separable, so a
526 1-dimensional kernel is enough. */
529 for(int i=-4; i<=4; ++i)
530 sum += (kernel[4+i] = exp(-i*i*0.5));
531 for(unsigned i=0; i<9; ++i)
534 unsigned char *line = new unsigned char[max(width, height)];
535 // Perform the blur in the X direction
536 for(unsigned y=0; y<height; ++y)
538 for(unsigned x=0; x<width; ++x)
541 for(int i=-4; i<=4; ++i)
542 value += data[(x+width+i)%width+y*width]*kernel[4+i];
545 copy(line, line+width, data+y*width);
547 // And then in the Y direction
548 for(unsigned x=0; x<width; ++x)
550 for(unsigned y=0; y<height; ++y)
553 for(int i=-4; i<=4; ++i)
554 value += data[x+((y+height+i)%height)*width]*kernel[4+i];
557 for(unsigned y=0; y<height; ++y)
558 data[x+y*width] = line[y];
563 void DesertPillars::create_normalmap(const unsigned char *bump, unsigned char *normals, unsigned width, unsigned height, float depth)
565 for(unsigned y=0; y<height; ++y)
566 for(unsigned x=0; x<width; ++x)
568 float dz_x = (bump[(x+1)%width+y*width]-bump[(x+width-1)%width+y*width])*depth/510;
569 float dz_y = (bump[x+((y+1)%height)*width]-bump[x+((y+height-1)%height)*width])*depth/510;
570 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
571 unsigned i = (x+y*width)*3;
572 normals[i] = (0.5-0.5*dz_x/l)*255;
573 normals[i+1] = (0.5-0.5*dz_y/l)*255;
574 normals[i+2] = (0.5+0.5/l)*255;
578 void DesertPillars::create_ground()
580 create_tiles_texture();
581 create_sand_texture();
583 ground_shdata.uniform("texture1", 0);
584 ground_shdata.uniform("normalmap1", 1);
585 ground_shdata.uniform("texture2", 2);
586 ground_shdata.uniform("normalmap2", 3);
588 GL::RenderPass *pass = &ground_tech.add_pass(0);
589 pass->set_shader_program(&ground_shprog, &ground_shdata);
590 pass->set_texture(0, &tiles_texture);
591 pass->set_texture(1, &tiles_normalmap);
592 pass->set_texture(2, &sand_texture);
593 pass->set_texture(3, &sand_normalmap);
595 /* No shadow pass here; the ground only receives shadows, but doesn't cast
598 GL::VertexFormat vfmt = (GL::VERTEX3, GL::NORMAL3, GL::TANGENT3, GL::BINORMAL3, GL::TEXCOORD2, GL::ATTRIB1,7);
599 ground_data.mesh = new GL::Mesh(vfmt);
601 // Create a base grid
602 GL::GridBuilder(80, 80, 200, 200).tbn().build(*ground_data.mesh);
604 // And modify it with a heightmap
605 unsigned n_vertices = ground_data.mesh->get_n_vertices();
606 unsigned pos = vfmt.offset(GL::VERTEX3);
607 unsigned nor = vfmt.offset(GL::NORMAL3);
608 unsigned tan = vfmt.offset(GL::TANGENT3);
609 unsigned bin = vfmt.offset(GL::BINORMAL3);
610 unsigned tex = vfmt.offset(GL::TEXCOORD2);
611 unsigned gt = vfmt.offset(GL::make_indexed_component(GL::ATTRIB1, 7));
612 for(unsigned i=0; i<n_vertices; ++i)
614 float *v = ground_data.mesh->modify_vertex(i);
615 v[pos+2] = ground_height(v[pos], v[pos+1]);
617 float dz_x = (ground_height(v[pos]+0.01, v[pos+1])-ground_height(v[pos]-0.01, v[pos+1]))/0.02;
618 float dz_y = (ground_height(v[pos], v[pos+1]+0.01)-ground_height(v[pos], v[pos+1]-0.01))/0.02;
619 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
624 l = sqrt(dz_x*dz_x+1);
628 l = sqrt(dz_y*dz_y+1);
632 v[gt] = min(v[pos+2]*100, 1.0f);
637 ground_data.object = new GL::Object(ground_data.mesh, &ground_tech);
639 scene.add(*ground_data.object);
642 float DesertPillars::ground_height(float x, float y)
644 // Leave a flat area in the middle
645 float d = sqrt(x*x+y*y);
649 // This results in concentric rings of low hills
650 int i = (d-6)/(M_PI*2);
651 float a = atan2(y, x);
654 float h = (i%2) ? 0.5 : 0.3;
655 return (d-6)*0.001f+(1-cos(d-6))*(1-cos(a))*h;
658 void DesertPillars::create_pillars()
660 // The pillars are a matt off-white
661 pillar_material.set_diffuse(GL::Color(0.9, 0.88, 0.8));
662 pillar_material.set_receive_shadows(true);
664 GL::RenderPass *pass = &pillar_tech.add_pass(0);
665 pass->set_material(&pillar_material);
667 pass = &pillar_tech.add_pass("shadow");
668 pass->set_shader_program(&shadow_shprog, 0);
670 pillar_data.reserve(7);
671 for(unsigned i=3; i<=20; ++i)
674 for(unsigned j=2; j<i; ++j)
678 if(pillar_data.size()<=height)
679 pillar_data.resize(height+1);
681 ObjectData &pd = pillar_data[height];
684 pd.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3));
685 GL::MeshBuilder bld(*pd.mesh);
687 // Produce a fluted cylinder
688 unsigned n_flutes = 12;
689 float r_bottom = cos(M_PI/n_flutes)*0.4;
690 float flute_depth = (0.4-r_bottom)*2;
691 float half_w = sin(M_PI/n_flutes)*0.4;
692 for(unsigned j=0; j<n_flutes; ++j)
694 float a = j*M_PI*2/n_flutes;
695 bld.begin(GL::TRIANGLE_STRIP);
696 for(int k=-3; k<=3; k+=2)
699 float x = cos(a)*(r_bottom-(1-t*t)*flute_depth)-sin(a)*t*half_w;
700 float y = sin(a)*(r_bottom-(1-t*t)*flute_depth)+cos(a)*t*half_w;
701 float d = -t*2*flute_depth;
702 float l = sqrt(d*d+1);
703 bld.normal(cos(a)/l-sin(a)*d/l, sin(a)/l+cos(a)*d/l, 0);
704 bld.vertex(x, y, 0.6+height);
705 bld.vertex(x, y, 0.6);
710 // Create a square plinth and capitel
711 bld.set_matrix(GL::Matrix::translation(0, 0, 0.3));
712 GL::BoxBuilder(1.0, 1.0, 0.6).build(bld);
713 bld.set_matrix(GL::Matrix::translation(0, 0, height+0.8));
714 GL::BoxBuilder(1.0, 1.0, 0.4).build(bld);
716 pd.object = new GL::Object(pd.mesh, &pillar_tech);
719 GL::AnimatedObject *pillar = new GL::AnimatedObject(*pd.object);
721 float a = (i-3)*2*M_PI/18;
722 matrix.translate(cos(a)*5, sin(a)*5, 0);
723 matrix.rotate(a, 0, 0, 1);
724 pillar->set_matrix(matrix);
726 pillars.push_back(pillar);
731 void DesertPillars::create_cube()
733 /* The cube is bluish-gray, with a hard specular reflection to produce a
735 cube_material.set_diffuse(GL::Color(0.5, 0.5, 0.55));
736 cube_material.set_specular(GL::Color(1.0));
737 cube_material.set_shininess(120);
738 cube_material.set_reflectivity(0.5);
740 GL::RenderPass *pass = &cube_tech.add_pass(0);
741 pass->set_material(&cube_material);
742 pass->set_shader_program(&cube_shprog, 0);
744 pass = &cube_tech.add_pass("shadow");
745 pass->set_shader_program(&cube_shadow_shprog, 0);
747 cube_data.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3, GL::ATTRIB3,7));
748 GL::MeshBuilder bld(*cube_data.mesh);
749 create_cube_face(bld, GL::Vector3(-1, -1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 2, 0), 16);
750 bld.offset(cube_data.mesh->get_n_vertices());
751 create_cube_face(bld, GL::Vector3(-1, 1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 0, 2), 16);
752 bld.offset(cube_data.mesh->get_n_vertices());
753 create_cube_face(bld, GL::Vector3(-1, 1, 1), GL::Vector3(2, 0, 0), GL::Vector3(0, -2, 0), 16);
754 bld.offset(cube_data.mesh->get_n_vertices());
755 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(-2, 0, 0), GL::Vector3(0, 0, 2), 16);
756 bld.offset(cube_data.mesh->get_n_vertices());
757 create_cube_face(bld, GL::Vector3(-1, -1, 1), GL::Vector3(0, 0, -2), GL::Vector3(0, 2, 0), 16);
758 bld.offset(cube_data.mesh->get_n_vertices());
759 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(0, 0, 2), GL::Vector3(0, 2, 0), 16);
760 cube_data.object = new GL::Object(cube_data.mesh, &cube_tech);
762 cube = new Cube(*cube_data.object);
763 env_cube = new GL::EnvironmentMap(512, *cube, env_pipeline);
764 scene.add(*env_cube);
767 void DesertPillars::create_cube_face(GL::MeshBuilder &bld, const GL::Vector3 &base, const GL::Vector3 &side1, const GL::Vector3 &side2, unsigned div)
769 /* The sides follow the cube map convention where the cross product points
770 inwards. Since the normal has to point outwards, reverse the order. */
772 n.x = side2.y*side1.z-side2.z*side1.y;
773 n.y = side2.z*side1.x-side2.x*side1.z;
774 n.z = side2.x*side1.y-side2.y*side1.x;
775 float l = sqrt(n.x*n.x+n.y*n.y+n.z*n.z);
776 bld.normal(n.x/l, n.y/l, n.z/l);
778 // Create vertices, with precomputed spherified coordinates
779 for(unsigned i=0; i<=div; ++i)
780 for(unsigned j=0; j<=div; ++j)
783 v.x = base.x+side1.x*i/div+side2.x*j/div;
784 v.y = base.y+side1.y*i/div+side2.y*j/div;
785 v.z = base.z+side1.z*i/div+side2.z*j/div;
787 l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
789 bld.attrib(7, v.x/l, v.y/l, v.z/l);
794 for(unsigned i=0; i<div; ++i)
796 bld.begin(GL::TRIANGLE_STRIP);
797 for(unsigned j=0; j<=div; ++j)
799 bld.element(i*(div+1)+j);
800 bld.element((i+1)*(div+1)+j);
806 int DesertPillars::main()
809 return Application::main();
812 void DesertPillars::tick()
814 Time::TimeStamp t = Time::now();
822 camera_angle += (dt/Time::sec)*M_PI*2/30;
823 if(camera_angle>M_PI*4)
824 camera_angle -= M_PI*4;
825 float h = 3+(1-cos(camera_angle*1.5))*3;
826 float r = sqrt(225-h*h);
827 camera.set_position(GL::Vector3(cos(camera_angle)*r, sin(camera_angle)*r, 1.5+h));
828 camera.look_at(GL::Vector3(0, 0, 2));
833 cube_angle += (dt/Time::sec)*M_PI*2/20;
834 GL::Matrix cube_matrix;
835 cube_matrix.translate(0, 0, 2.5);
836 cube_matrix.rotate(cube_angle, 0, 0, 1);
837 cube_matrix.rotate(cube_angle*0.5, 1, 0, 0);
838 cube->set_matrix(cube_matrix);
843 cube_phase += (dt/Time::sec)/5;
853 float x = cube_phase*5;
855 cube->set_spherify((1-x)*cube_shapes[(cube_shape+3)%4]+x*cube_shapes[cube_shape]);
858 cube->set_spherify(cube_shapes[cube_shape]);
865 void DesertPillars::key_press(unsigned key)
867 if(key==Input::KEY_ESC)
869 else if(key==Input::KEY_SPACE)
870 camera_stopped = !camera_stopped;
871 else if(key==Input::KEY_F)
872 cube_frozen = !cube_frozen;
873 else if(key==Input::KEY_S)
874 cube_stopped = !cube_stopped;
878 DesertPillars::ObjectData::ObjectData():
883 DesertPillars::ObjectData::~ObjectData()
890 DesertPillars::Cube::Cube(const GL::Object &obj):
891 GL::AnimatedObject(obj)
894 void DesertPillars::Cube::set_spherify(float s)
896 shdata.uniform("spherify", s);
899 void DesertPillars::Cube::setup_render(GL::Renderer &renderer, const GL::Tag &tag) const
901 AnimatedObject::setup_render(renderer, tag);
902 renderer.add_shader_data(shdata);