-#include <cmath>
-#include <cstdlib>
-#include <msp/core/application.h>
-#include <msp/core/getopt.h>
-#include <msp/gl/animatedobject.h>
-#include <msp/gl/bloom.h>
-#include <msp/gl/box.h>
-#include <msp/gl/colorcurve.h>
-#include <msp/gl/cylinder.h>
-#include <msp/gl/environmentmap.h>
-#include <msp/gl/framebuffer.h>
-#include <msp/gl/grid.h>
-#include <msp/gl/instancescene.h>
-#include <msp/gl/light.h>
-#include <msp/gl/lighting.h>
-#include <msp/gl/basicmaterial.h>
-#include <msp/gl/mesh.h>
-#include <msp/gl/meshbuilder.h>
-#include <msp/gl/object.h>
-#include <msp/gl/pipeline.h>
-#include <msp/gl/program.h>
-#include <msp/gl/renderer.h>
-#include <msp/gl/resources.h>
-#include <msp/gl/shader.h>
-#include <msp/gl/shadowmap.h>
-#include <msp/gl/simplescene.h>
-#include <msp/gl/technique.h>
-#include <msp/gl/tests.h>
-#include <msp/gl/texturecube.h>
-#include <msp/gl/windowview.h>
-#include <msp/graphics/simplewindow.h>
-#include <msp/input/keyboard.h>
-#include <msp/input/keys.h>
-#include <msp/time/timestamp.h>
-#include <msp/time/timedelta.h>
-#include <msp/time/utils.h>
-
-using namespace std;
-using namespace Msp;
-
-/**
-This application demonstrates a variety of features of the mspgl library,
-including:
-- Creating meshes from multiple parts
-- Creating a mesh and then modifying it
-- Shadow mapping
-- Environment mapped reflections
-- Skybox using a cube map texture
-- Effects with nested pipelines
-- Complex multitexturing
-- Shader-based deformations
-- Creating a normalmapped texture through rendering
-
-To run the program in fullscreen mode, specify --fullscreen on the command
-line.
-
-During execution the following keys are available:
-esc exit
-space stop camera movement
-s stop cube rotation
-f freeze cube shape
-*/
-class DesertPillars: public RegisteredApplication<DesertPillars>
-{
-private:
- struct Options
- {
- Graphics::WindowOptions window_opts;
-
- Options(const Graphics::Display &, int, char **);
- };
-
- struct ObjectData
- {
- GL::Mesh *mesh;
- GL::Object *object;
-
- ObjectData();
- ~ObjectData();
- };
-
- class Cube: public GL::AnimatedObject
- {
- private:
- GL::ProgramData shdata;
-
- public:
- Cube(const GL::Object &);
-
- void set_spherify(float);
-
- virtual void setup_render(GL::Renderer &, const GL::Tag &) const;
- };
-
- Msp::Graphics::Display display;
- Options options;
- Msp::Graphics::Window window;
- Msp::Graphics::GLContext gl_context;
- Msp::Input::Keyboard keyboard;
- GL::Resources resources;
-
- GL::Program skybox_shprog;
- GL::Technique skybox_tech;
- GL::TextureCube skybox_tex;
- GL::Sampler linear_clamped_sampler;
- ObjectData skybox_data;
-
- const GL::Program &shadow_shprog;
-
- GL::Program ground_shprog;
- GL::ProgramData ground_shdata;
- GL::Texture2D tiles_texture;
- GL::Texture2D tiles_normalmap;
- GL::Texture2D sand_texture;
- GL::Texture2D sand_normalmap;
- GL::Sampler linear_sampler;
- GL::Sampler mipmap_sampler;
- GL::Technique ground_tech;
- ObjectData ground_data;
-
- GL::BasicMaterial pillar_material;
- GL::Technique pillar_tech;
- std::vector<ObjectData> pillar_data;
- std::vector<GL::AnimatedObject *> pillars;
-
- GL::Program cube_shprog;
- GL::Program cube_shadow_shprog;
- GL::BasicMaterial cube_material;
- GL::Technique cube_tech;
- ObjectData cube_data;
- Cube *cube;
- GL::EnvironmentMap *env_cube;
-
- GL::WindowView view;
- GL::Pipeline pipeline;
- GL::Camera camera;
- GL::SimpleScene sky_scene;
- GL::InstanceScene scene;
- GL::Lighting lighting;
- GL::Light light;
- GL::ShadowMap shadow_scene;
- GL::Bloom bloom;
- GL::ColorCurve colorcurve;
-
- GL::Pipeline env_pipeline;
-
- Time::TimeStamp last_tick;
- float camera_angle;
- bool camera_stopped;
- float cube_angle;
- bool cube_stopped;
- unsigned cube_shape;
- float cube_phase;
- bool cube_frozen;
-
- static const char texture_src[];
- static const char skybox_src[];
- static const char ground_src[];
- static const char cube_src[];
- static const char cube_shadow_src_tail[];
- static const float cube_shapes[];
-
-public:
- DesertPillars(int, char **);
- ~DesertPillars();
-
-private:
- void setup_view();
- void create_pipeline();
- void create_skybox();
- static void create_skybox_face(GL::TextureCube &, GL::TextureCubeFace);
- void create_tiles_texture();
- void create_sand_texture();
- static void gaussian_blur(unsigned char *, unsigned, unsigned);
- static void create_normalmap(const unsigned char *, unsigned char *, unsigned, unsigned, float);
- void create_ground();
- static float ground_height(float, float);
- void create_pillars();
- void create_cube();
- static void create_cube_face(GL::MeshBuilder &, const GL::Vector3 &, const GL::Vector3 &, const GL::Vector3 &, unsigned);
-
-public:
- virtual int main();
-private:
- virtual void tick();
-
- void key_press(unsigned);
-};
-
-const char DesertPillars::texture_src[] =
- "import msp_interface;\n"
- "#pragma MSP stage(vertex)\n"
- "void main()\n"
- "{\n"
- " gl_Position = vec4(vertex.xy*2.0-1.0, -vertex.z*2.0, 1.0);\n"
- " passthrough;\n"
- "}\n"
- "#pragma MSP stage(fragment)\n"
- "layout(location=1) out vec4 frag_normal;\n"
- "void main()\n"
- "{\n"
- " frag_color = color;\n"
- " frag_normal = vec4(normal*0.5+0.5, 1.0);\n"
- "}\n";
-
-const char DesertPillars::skybox_src[] =
- "import msp_interface;\n"
- "uniform samplerCube sky;\n"
- "#pragma MSP stage(vertex)\n"
- "void main()\n"
- "{\n"
- " gl_Position = projection_matrix*vec4(mat3(eye_obj_matrix)*vertex.xyz, 1.0);\n"
- " passthrough;\n"
- "}\n"
- "#pragma MSP stage(fragment)\n"
- "void main()\n"
- "{\n"
- " frag_color = texture(sky, vertex.xyz);\n"
- "}\n";
-
-const char DesertPillars::ground_src[] =
- "import phong;\n"
- "uniform sampler2D texture1;\n"
- "uniform sampler2D normalmap1;\n"
- "uniform sampler2D texture2;\n"
- "uniform sampler2D normalmap2;\n"
- "const bool use_normal_map = true;\n"
- "const bool use_shadow_map = true;\n"
- "#pragma MSP stage(vertex)\n"
- "layout(location=7) in float ground_type;\n"
- "#pragma MSP stage(fragment)\n"
- "vec4 get_diffuse_color()\n"
- "{\n"
- " return mix(texture(texture1, texcoord.xy*3.0), texture(texture2, texcoord.xy), ground_type);\n"
- "}\n"
- "vec4 get_fragment_normal()\n"
- "{\n"
- " return mix(texture(normalmap1, texcoord.xy*3.0).rgb, texture(normalmap2, texcoord.xy).rgb, ground_type);\n"
- "}\n";
-
-const char DesertPillars::cube_src[] =
- "import phong;\n"
- "const bool use_specular = true;\n"
- "const bool use_reflectivity = true;\n"
- "uniform float spherify;\n"
- "#pragma MSP stage(vertex)\n"
- "layout(location=7) in vec3 sphere_coord;\n"
- "vec4 transform_position(vec4 pos)\n"
- "{\n"
- " return eye_obj_matrix*vec4(mix(vertex.xyz, sphere_coord, spherify), 1.0);\n"
- "}\n"
- "vec3 transform_normal(vec3 pos)\n"
- "{\n"
- " return eye_obj_normal_matrix*normalize(mix(normal, normalize(sphere_coord), spherify));\n"
- "}\n";
-
-const char DesertPillars::cube_shadow_src_tail[] =
- "#pragma MSP stage(fragment)\n"
- "void main()\n"
- "{\n"
- " frag_color = vec4(1.0);\n"
- "}\n";
-
-const float DesertPillars::cube_shapes[] = { -0.4, 0.5, 1.0, 0.3 };
-
-
-DesertPillars::Options::Options(const Graphics::Display &dpy, int argc, char **argv)
-{
- GetOpt getopt;
- getopt.add_option('f', "fullscreen", window_opts.fullscreen, GetOpt::NO_ARG).set_help("Run in fullscreen mode");
- getopt(argc, argv);
-
- if(window_opts.fullscreen)
- {
- const Graphics::VideoMode &mode = dpy.get_desktop_mode();
- window_opts.width = mode.width;
- window_opts.height = mode.height;
- }
- else
- {
- window_opts.width = 800;
- window_opts.height = 600;
- }
-}
-
-
-DesertPillars::DesertPillars(int argc, char **argv):
- options(display, argc, argv),
- window(display, options.window_opts),
- gl_context(window),
- keyboard(window),
- skybox_shprog(skybox_src),
- shadow_shprog(resources.get<GL::Program>("_occluder.glsl.shader")),
- ground_shprog(ground_src),
- cube_shprog(cube_src),
- cube_shadow_shprog(string(cube_src)+cube_shadow_src_tail),
- view(window, gl_context),
- pipeline(view),
- shadow_scene(resources, 2048, scene, light),
- bloom(resources, window.get_width(), window.get_height()),
- colorcurve(resources),
- env_pipeline(512, 512),
- camera_angle(0),
- camera_stopped(false),
- cube_angle(0),
- cube_stopped(false),
- cube_shape(0),
- cube_phase(0),
- cube_frozen(false)
-{
- window.set_title("Desert Pillars");
- window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &DesertPillars::exit), 0));
- if(options.window_opts.fullscreen)
- window.show_cursor(false);
- keyboard.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &DesertPillars::key_press), false));
-
- setup_view();
- create_pipeline();
- create_skybox();
- create_ground();
- create_pillars();
- create_cube();
-}
-
-DesertPillars::~DesertPillars()
-{
- delete env_cube;
- delete cube;
- for(vector<GL::AnimatedObject *>::iterator i=pillars.begin(); i!=pillars.end(); ++i)
- delete *i;
-}
-
-void DesertPillars::setup_view()
-{
- camera.set_aspect_ratio(float(window.get_width())/window.get_height());
- camera.set_up_direction(GL::Vector3(0, 0, 1));
- camera.set_depth_clip(1, 50);
- view.set_camera(&camera);
- view.set_content(&pipeline);
-}
-
-void DesertPillars::create_pipeline()
-{
- pipeline.set_multisample(8);
- pipeline.set_hdr(true);
-
- /* The shadow map is focused on the part of the scene that contains the
- pillars and the cube. Making the ground cast shadows as well would result
- either in a very low spatial resolution of the shadow map, or ugly artifacts
- as the ground crosses the shadow map boundary. */
- shadow_scene.set_target(GL::Vector3(0, 0, 0), 10);
- shadow_scene.set_darkness(1);
-
- // Put the sun pretty high in the sky
- light.set_diffuse(GL::Color(2.0));
- light.set_position(GL::Vector4(0.5, -2, 3, 0));
- lighting.attach(0, light);
- lighting.set_ambient(GL::Color(0.2));
-
- // The skybox is rendered first
- pipeline.add_pass(0, sky_scene);
-
- GL::Pipeline::Pass *pass = &pipeline.add_pass(0, shadow_scene);
- pass->set_lighting(&lighting);
- pass->set_depth_test(&GL::DepthTest::lequal());
-
- /* A bloom filter enhances the realism of bright surfaces, even if there
- isn't anything really glowy in the scene. */
- bloom.set_strength(0.3);
- pipeline.add_postprocessor(bloom);
-
- /* Lighting calculations are best done in linear color space, so the final
- image must be converted to srgb for display. */
- colorcurve.set_srgb();
- pipeline.add_postprocessor(colorcurve);
-
- /* Initialize a second pipeline to render the environment map. It has the
- same renderables and passes, but no postprocessors or camera. */
- env_pipeline.add_pass(0, sky_scene);
- pass = &env_pipeline.add_pass(0, shadow_scene);
- pass->set_lighting(&lighting);
- pass->set_depth_test(&GL::DepthTest::lequal());
-}
-
-void DesertPillars::create_skybox()
-{
- skybox_tex.storage(GL::SRGB8, 128, 1);
- linear_clamped_sampler.set_min_filter(GL::LINEAR);
- linear_clamped_sampler.set_wrap(GL::CLAMP_TO_EDGE);
- for(unsigned i=0; i<6; ++i)
- create_skybox_face(skybox_tex, skybox_tex.enumerate_faces(i));
-
- GL::RenderPass &pass = skybox_tech.add_pass(0);
- pass.set_shader_program(&skybox_shprog, 0);
- pass.set_texture(0, &skybox_tex, &linear_clamped_sampler);
-
- // The shader will use the vertex coordinates to initialize texture coordinates as well
- skybox_data.mesh = new GL::Mesh(GL::VERTEX3);
- GL::BoxBuilder(10, 10, 10).build(*skybox_data.mesh);
- skybox_data.object = new GL::Object(skybox_data.mesh, &skybox_tech);
-
- sky_scene.add(*skybox_data.object);
-}
-
-void DesertPillars::create_skybox_face(GL::TextureCube &texture, GL::TextureCubeFace face)
-{
- unsigned char *pixels = new unsigned char[128*128*3];
- for(int y=0; y<128; ++y)
- for(int x=0; x<128; ++x)
- {
- unsigned i = (x+y*128)*3;
- GL::Vector3 v = texture.get_texel_direction(face, x, y);
- if(v.z>0)
- {
- float l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
- // Render a sky-like gradient, with deeper blue at the zenith
- pixels[i] = 96-48*v.z/l;
- pixels[i+1] = 168-84*v.z/l;
- pixels[i+2] = 255;
- }
- else
- {
- // Fill with a desert-y color below horizon
- pixels[i] = 240;
- pixels[i+1] = 224;
- pixels[i+2] = 160;
- }
- }
- texture.image(face, 0, pixels);
- delete[] pixels;
-}
-
-void DesertPillars::create_tiles_texture()
-{
- unsigned width = 256;
- unsigned height = 256;
- tiles_texture.storage(GL::RGB8, width, height, 1);
- tiles_normalmap.storage(GL::RGB8, width, height, 1);
- linear_sampler.set_min_filter(GL::LINEAR);
-
- GL::Mesh tiles((GL::VERTEX3, GL::NORMAL3, GL::COLOR4_UBYTE));
-
- // Prepare some lookup tables for rendering the tiles
- float split = 1.0f/3;
- float spacing = split*0.02f;
- float bevel = split*0.1f;
- float coords[] = { 0.0f, spacing, spacing+bevel, split-bevel, split, split+spacing, split+spacing+bevel, 1.0f-bevel, 1.0f };
- unsigned order[] = { 4, 1, 3, 2, 1, 1, 2, 2,
- 1, 4, 2, 3, 4, 4, 3, 3,
- 1, 1, 2, 2, 1, 4, 2, 3,
- 4, 4, 3, 3, 4, 1, 3, 2,
- 2, 3, 2, 2, 3, 3, 3, 2 };
-
- {
- GL::MeshBuilder bld(tiles);
-
- // Create a dark background
- bld.color(0.2f, 0.2f, 0.2f);
- bld.normal(0.0f, 0.0f, 1.0f);
- bld.begin(GL::TRIANGLE_STRIP);
- bld.vertex(0.0f, 1.0f);
- bld.vertex(0.0f, 0.0f);
- bld.vertex(1.0f, 1.0f);
- bld.vertex(1.0f, 0.0f);
- bld.end();
-
- // Create the four tiles
- bld.color(GL::Color(0.95f, 0.8f, 0.65f).to_linear());
- for(unsigned i=0; i<2; ++i)
- for(unsigned j=0; j<2; ++j)
- {
- for(unsigned k=0; k<4; ++k)
- {
- bld.begin(GL::TRIANGLE_STRIP);
- float facing = (k%2)*2-1.0f;
- if(k<2)
- bld.normal(0.0f, 0.7071f*facing, 0.7071f);
- else
- bld.normal(0.7071f*facing, 0.0f, 0.7071f);
- for(unsigned l=0; l<4; ++l)
- bld.vertex(coords[i*4+order[k*8+l*2]], coords[j*4+order[k*8+l*2+1]], (l%2 ? bevel : 0.0f));
- bld.end();
- }
-
- bld.begin(GL::TRIANGLE_STRIP);
- bld.normal(0.0f, 0.0f, 1.0f);
- for(unsigned l=0; l<4; ++l)
- bld.vertex(coords[i*4+order[32+l*2]], coords[j*4+order[32+l*2+1]], bevel);
- bld.end();
- }
- }
-
- GL::Program shprog(texture_src);
-
- // Use an FBO to turn the geometry into a normalmapped texture
- GL::Framebuffer fbo;
- fbo.attach(GL::COLOR_ATTACHMENT0, tiles_texture);
- fbo.attach(GL::COLOR_ATTACHMENT1, tiles_normalmap);
- GL::Bind bind_fbo(fbo);
- GL::Renderer renderer;
- renderer.set_shader_program(&shprog, 0);
- tiles.draw(renderer);
-}
-
-void DesertPillars::create_sand_texture()
-{
- unsigned width = 512;
- unsigned height = 512;
-
- sand_texture.storage(GL::SRGB8, width/16, height/16);
- sand_texture.set_auto_generate_mipmap(true);
- sand_normalmap.storage(GL::RGB8, width, height);
- sand_normalmap.set_auto_generate_mipmap(true);
- mipmap_sampler.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
- mipmap_sampler.set_max_anisotropy(4);
-
- unsigned char *pixels = new unsigned char[width*height*3];
- unsigned char *bump = new unsigned char[width*height];
- for(unsigned y=0; y<height; ++y)
- for(unsigned x=0; x<width; ++x)
- {
- unsigned i = (x+y*width)*3;
- unsigned c = rand()%16;
- pixels[i] = 224+c;
- pixels[i+1] = 208+c;
- pixels[i+2] = 160;
- bump[x+y*width] = rand();
- }
- sand_texture.image(0, pixels);
- gaussian_blur(bump, width, height);
- create_normalmap(bump, pixels, width, height, 4);
- sand_normalmap.image(0, pixels);
- delete[] pixels;
- delete[] bump;
-}
-
-void DesertPillars::gaussian_blur(unsigned char *data, unsigned width, unsigned height)
-{
- /* Create a gaussian blur kernel for σ=2. Gaussian blur is separable, so a
- 1-dimensional kernel is enough. */
- float kernel[9];
- float sum = 0;
- for(int i=-4; i<=4; ++i)
- sum += (kernel[4+i] = exp(-i*i*0.5));
- for(unsigned i=0; i<9; ++i)
- kernel[i] /= sum;
-
- unsigned char *line = new unsigned char[max(width, height)];
- // Perform the blur in the X direction
- for(unsigned y=0; y<height; ++y)
- {
- for(unsigned x=0; x<width; ++x)
- {
- float value = 0;
- for(int i=-4; i<=4; ++i)
- value += data[(x+width+i)%width+y*width]*kernel[4+i];
- line[x] = value;
- }
- copy(line, line+width, data+y*width);
- }
- // And then in the Y direction
- for(unsigned x=0; x<width; ++x)
- {
- for(unsigned y=0; y<height; ++y)
- {
- float value = 0;
- for(int i=-4; i<=4; ++i)
- value += data[x+((y+height+i)%height)*width]*kernel[4+i];
- line[y] = value;
- }
- for(unsigned y=0; y<height; ++y)
- data[x+y*width] = line[y];
- }
- delete[] line;
-}
-
-void DesertPillars::create_normalmap(const unsigned char *bump, unsigned char *normals, unsigned width, unsigned height, float depth)
-{
- for(unsigned y=0; y<height; ++y)
- for(unsigned x=0; x<width; ++x)
- {
- float dz_x = (bump[(x+1)%width+y*width]-bump[(x+width-1)%width+y*width])*depth/510;
- float dz_y = (bump[x+((y+1)%height)*width]-bump[x+((y+height-1)%height)*width])*depth/510;
- float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
- unsigned i = (x+y*width)*3;
- normals[i] = (0.5-0.5*dz_x/l)*255;
- normals[i+1] = (0.5-0.5*dz_y/l)*255;
- normals[i+2] = (0.5+0.5/l)*255;
- }
-}
-
-void DesertPillars::create_ground()
-{
- create_tiles_texture();
- create_sand_texture();
-
- ground_shdata.uniform("texture1", 0);
- ground_shdata.uniform("normalmap1", 1);
- ground_shdata.uniform("texture2", 2);
- ground_shdata.uniform("normalmap2", 3);
-
- GL::RenderPass *pass = &ground_tech.add_pass(0);
- pass->set_shader_program(&ground_shprog, &ground_shdata);
- pass->set_texture(0, &tiles_texture, &linear_sampler);
- pass->set_texture(1, &tiles_normalmap, &linear_sampler);
- pass->set_texture(2, &sand_texture, &mipmap_sampler);
- pass->set_texture(3, &sand_normalmap, &mipmap_sampler);
-
- /* No shadow pass here; the ground only receives shadows, but doesn't cast
- them. */
-
- GL::VertexFormat vfmt = (GL::VERTEX3, GL::NORMAL3, GL::TANGENT3, GL::BINORMAL3, GL::TEXCOORD2, GL::ATTRIB1,7);
- ground_data.mesh = new GL::Mesh(vfmt);
-
- // Create a base grid
- GL::GridBuilder(80, 80, 200, 200).tbn().build(*ground_data.mesh);
-
- // And modify it with a heightmap
- unsigned n_vertices = ground_data.mesh->get_n_vertices();
- unsigned pos = vfmt.offset(GL::VERTEX3);
- unsigned nor = vfmt.offset(GL::NORMAL3);
- unsigned tan = vfmt.offset(GL::TANGENT3);
- unsigned bin = vfmt.offset(GL::BINORMAL3);
- unsigned tex = vfmt.offset(GL::TEXCOORD2);
- unsigned gt = vfmt.offset(GL::make_indexed_component(GL::ATTRIB1, 7));
- for(unsigned i=0; i<n_vertices; ++i)
- {
- float *v = ground_data.mesh->modify_vertex(i);
- v[pos+2] = ground_height(v[pos], v[pos+1]);
-
- float dz_x = (ground_height(v[pos]+0.01, v[pos+1])-ground_height(v[pos]-0.01, v[pos+1]))/0.02;
- float dz_y = (ground_height(v[pos], v[pos+1]+0.01)-ground_height(v[pos], v[pos+1]-0.01))/0.02;
- float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
- v[nor] = -dz_x/l;
- v[nor+1] = -dz_y/l;
- v[nor+2] = 1/l;
-
- l = sqrt(dz_x*dz_x+1);
- v[tan] = 1/l;
- v[tan+2] = dz_x/l;
-
- l = sqrt(dz_y*dz_y+1);
- v[bin+1] = 1/l;
- v[bin+2] = dz_y/l;
-
- v[gt] = min(v[pos+2]*100, 1.0f);
-
- v[tex] *= 20;
- v[tex+1] *= 20;
- }
- ground_data.object = new GL::Object(ground_data.mesh, &ground_tech);
-
- scene.add(*ground_data.object);
-}
-
-float DesertPillars::ground_height(float x, float y)
-{
- // Leave a flat area in the middle
- float d = sqrt(x*x+y*y);
- if(d<=6.0f)
- return 0;
-
- // This results in concentric rings of low hills
- int i = (d-6)/(M_PI*2);
- float a = atan2(y, x);
- a *= i*2+5;
- a += M_PI*(i%3)/2;
- float h = (i%2) ? 0.5 : 0.3;
- return (d-6)*0.001f+(1-cos(d-6))*(1-cos(a))*h;
-}
-
-void DesertPillars::create_pillars()
-{
- // The pillars are a matt off-white
- pillar_material.set_diffuse(GL::Color(0.9, 0.89, 0.85).to_linear());
- pillar_material.set_receive_shadows(true);
-
- GL::RenderPass *pass = &pillar_tech.add_pass(0);
- pass->set_material(&pillar_material);
-
- pass = &pillar_tech.add_pass("shadow");
- pass->set_shader_program(&shadow_shprog, 0);
-
- pillar_data.reserve(7);
- for(unsigned i=3; i<=20; ++i)
- {
- unsigned height = 2;
- for(unsigned j=2; j<i; ++j)
- if(i%j==0)
- ++height;
-
- if(pillar_data.size()<=height)
- pillar_data.resize(height+1);
-
- ObjectData &pd = pillar_data[height];
- if(!pd.object)
- {
- pd.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3));
- GL::MeshBuilder bld(*pd.mesh);
-
- // Produce a fluted cylinder
- unsigned n_flutes = 12;
- float r_bottom = cos(M_PI/n_flutes)*0.4;
- float flute_depth = (0.4-r_bottom)*2;
- float half_w = sin(M_PI/n_flutes)*0.4;
- for(unsigned j=0; j<n_flutes; ++j)
- {
- float a = j*M_PI*2/n_flutes;
- bld.begin(GL::TRIANGLE_STRIP);
- for(int k=-3; k<=3; k+=2)
- {
- float t = k/3.0f;
- float x = cos(a)*(r_bottom-(1-t*t)*flute_depth)-sin(a)*t*half_w;
- float y = sin(a)*(r_bottom-(1-t*t)*flute_depth)+cos(a)*t*half_w;
- float d = -t*2*flute_depth;
- float l = sqrt(d*d+1);
- bld.normal(cos(a)/l-sin(a)*d/l, sin(a)/l+cos(a)*d/l, 0);
- bld.vertex(x, y, 0.6+height);
- bld.vertex(x, y, 0.6);
- }
- bld.end();
- }
-
- // Create a square plinth and capitel
- bld.set_matrix(GL::Matrix::translation(0, 0, 0.3));
- GL::BoxBuilder(1.0, 1.0, 0.6).build(bld);
- bld.set_matrix(GL::Matrix::translation(0, 0, height+0.8));
- GL::BoxBuilder(1.0, 1.0, 0.4).build(bld);
-
- pd.object = new GL::Object(pd.mesh, &pillar_tech);
- }
-
- GL::AnimatedObject *pillar = new GL::AnimatedObject(*pd.object);
- GL::Matrix matrix;
- float a = (i-3)*2*M_PI/18;
- matrix.translate(cos(a)*5, sin(a)*5, 0);
- matrix.rotate(a, 0, 0, 1);
- pillar->set_matrix(matrix);
-
- pillars.push_back(pillar);
- scene.add(*pillar);
- }
-}
-
-void DesertPillars::create_cube()
-{
- /* The cube is bluish-gray, with a hard specular reflection to produce a
- sun-like spot */
- cube_material.set_diffuse(GL::Color(0.5, 0.5, 0.55).to_linear());
- cube_material.set_specular(GL::Color(1.0));
- cube_material.set_shininess(120);
- cube_material.set_reflectivity(0.5);
-
- GL::RenderPass *pass = &cube_tech.add_pass(0);
- pass->set_material(&cube_material);
- pass->set_shader_program(&cube_shprog, 0);
-
- pass = &cube_tech.add_pass("shadow");
- pass->set_shader_program(&cube_shadow_shprog, 0);
-
- cube_data.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3, GL::ATTRIB3,7));
- GL::MeshBuilder bld(*cube_data.mesh);
- create_cube_face(bld, GL::Vector3(-1, -1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 2, 0), 16);
- bld.offset(cube_data.mesh->get_n_vertices());
- create_cube_face(bld, GL::Vector3(-1, 1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 0, 2), 16);
- bld.offset(cube_data.mesh->get_n_vertices());
- create_cube_face(bld, GL::Vector3(-1, 1, 1), GL::Vector3(2, 0, 0), GL::Vector3(0, -2, 0), 16);
- bld.offset(cube_data.mesh->get_n_vertices());
- create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(-2, 0, 0), GL::Vector3(0, 0, 2), 16);
- bld.offset(cube_data.mesh->get_n_vertices());
- create_cube_face(bld, GL::Vector3(-1, -1, 1), GL::Vector3(0, 0, -2), GL::Vector3(0, 2, 0), 16);
- bld.offset(cube_data.mesh->get_n_vertices());
- create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(0, 0, 2), GL::Vector3(0, 2, 0), 16);
- cube_data.object = new GL::Object(cube_data.mesh, &cube_tech);
-
- cube = new Cube(*cube_data.object);
- env_cube = new GL::EnvironmentMap(resources, 512, *cube, env_pipeline);
- scene.add(*env_cube);
-}
-
-void DesertPillars::create_cube_face(GL::MeshBuilder &bld, const GL::Vector3 &base, const GL::Vector3 &side1, const GL::Vector3 &side2, unsigned div)
-{
- /* The sides follow the cube map convention where the cross product points
- inwards. Since the normal has to point outwards, reverse the order. */
- GL::Vector3 n;
- n.x = side2.y*side1.z-side2.z*side1.y;
- n.y = side2.z*side1.x-side2.x*side1.z;
- n.z = side2.x*side1.y-side2.y*side1.x;
- float l = sqrt(n.x*n.x+n.y*n.y+n.z*n.z);
- bld.normal(n.x/l, n.y/l, n.z/l);
-
- // Create vertices, with precomputed spherified coordinates
- for(unsigned i=0; i<=div; ++i)
- for(unsigned j=0; j<=div; ++j)
- {
- GL::Vector3 v;
- v.x = base.x+side1.x*i/div+side2.x*j/div;
- v.y = base.y+side1.y*i/div+side2.y*j/div;
- v.z = base.z+side1.z*i/div+side2.z*j/div;
-
- l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
- l /= 1.732;
- bld.attrib(7, v.x/l, v.y/l, v.z/l);
-
- bld.vertex(v);
- }
-
- for(unsigned i=0; i<div; ++i)
- {
- bld.begin(GL::TRIANGLE_STRIP);
- for(unsigned j=0; j<=div; ++j)
- {
- bld.element(i*(div+1)+j);
- bld.element((i+1)*(div+1)+j);
- }
- bld.end();
- }
-}
-
-int DesertPillars::main()
-{
- window.show();
- return Application::main();
-}
-
-void DesertPillars::tick()
-{
- Time::TimeStamp t = Time::now();
- Time::TimeDelta dt;
- if(last_tick)
- dt = t-last_tick;
- last_tick = t;
-
- if(!camera_stopped)
- {
- camera_angle += (dt/Time::sec)*M_PI*2/30;
- if(camera_angle>M_PI*4)
- camera_angle -= M_PI*4;
- float h = 3+(1-cos(camera_angle*1.5))*3;
- float r = sqrt(225-h*h);
- camera.set_position(GL::Vector3(cos(camera_angle)*r, sin(camera_angle)*r, 1.5+h));
- camera.look_at(GL::Vector3(0, 0, 2));
- }
-
- if(!cube_stopped)
- {
- cube_angle += (dt/Time::sec)*M_PI*2/20;
- GL::Matrix cube_matrix;
- cube_matrix.translate(0, 0, 2.5);
- cube_matrix.rotate(cube_angle, 0, 0, 1);
- cube_matrix.rotate(cube_angle*0.5, 1, 0, 0);
- cube->set_matrix(cube_matrix);
- }
-
- if(!cube_frozen)
- {
- cube_phase += (dt/Time::sec)/5;
- if(cube_phase>1)
- {
- cube_phase -= 1;
- ++cube_shape;
- if(cube_shape>=4)
- cube_shape -= 4;
- }
- if(cube_phase<0.2)
- {
- float x = cube_phase*5;
- x = (3-2*x)*x*x;
- cube->set_spherify((1-x)*cube_shapes[(cube_shape+3)%4]+x*cube_shapes[cube_shape]);
- }
- else
- cube->set_spherify(cube_shapes[cube_shape]);
- }
-
- display.tick();
- view.render();
-}
-
-void DesertPillars::key_press(unsigned key)
-{
- if(key==Input::KEY_ESC)
- exit(0);
- else if(key==Input::KEY_SPACE)
- camera_stopped = !camera_stopped;
- else if(key==Input::KEY_F)
- cube_frozen = !cube_frozen;
- else if(key==Input::KEY_S)
- cube_stopped = !cube_stopped;
-}
-
-
-DesertPillars::ObjectData::ObjectData():
- mesh(0),
- object(0)
-{ }
-
-DesertPillars::ObjectData::~ObjectData()
-{
- delete object;
- delete mesh;
-}
-
-
-DesertPillars::Cube::Cube(const GL::Object &obj):
- GL::AnimatedObject(obj)
-{ }
-
-void DesertPillars::Cube::set_spherify(float s)
-{
- shdata.uniform("spherify", s);
-}
-
-void DesertPillars::Cube::setup_render(GL::Renderer &renderer, const GL::Tag &tag) const
-{
- AnimatedObject::setup_render(renderer, tag);
- renderer.add_shader_data(shdata);
-}