예제 #1
0
image3f read_pnm(const string& filename, bool flipY) {
    int width, height, nc; float scale; unsigned char* buffer; char type;
    _read_pnm(filename, type, width, height, nc, scale, buffer);
    if (not buffer) {
        error_if_not(false, "failed to load image file %s", filename.c_str());
        return image3f();
    }
    error_if_not(nc == 3 && (type == 'f' || type == 'B'), "unsupported image format in file %s", filename.c_str());
    
    image3f img(width,height);
    if(type == 'f') {
        float* buf = (float*)buffer;
        for(int i = 0; i < img.width()*img.height(); i ++) {
            img.data()[i] = vec3f(buf[i*3+0],buf[i*3+1],buf[i*3+2]) * scale;
        }
    } else if(type == 'B') {
        unsigned char* buf = (unsigned char*)buffer;
        for(int i = 0; i < img.width()*img.height(); i ++) {
            img.data()[i] = vec3f((float)buf[i*3+0],(float)buf[i*3+1],(float)buf[i*3+2]) * scale;
        }        
    }
    if (buffer) delete [] buffer;
    
    if(flipY) img = img.flipy();
    
    return img;
}
예제 #2
0
// uiloop
void uiloop() {
    int w = 200, h = 200;
    // init glfw
    auto glfw_ok = glfwInit();
    error_if_not(glfw_ok, "glfw init error");
    
    // create window
    auto window = glfwCreateWindow(w, h, "graphics13 | build", NULL, NULL);
    error_if_not(window, "glfw window error");
    glfwMakeContextCurrent(window);
    
    // init glew
    auto glew_ok = glewInit();
    error_if_not(GLEW_OK == glew_ok, "glew init error");
    
    // run a few cycles
    for(int i = 0; i < 10; i++) {
        glfwGetFramebufferSize(window, &w, &h);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    // kill window and terminate glfw
    glfwDestroyWindow(window);
    glfwTerminate();
}
예제 #3
0
static void _read_pnm(const string& filename, char& type,
               int& width, int& height, int& nc,
               float& scale, unsigned char*& buffer) {
    char identifier[4]; 
    bool ascii = false;
    
    FILE *f = fopen(filename.c_str(), "rb");
    if (f == 0) {
        error_if_not(f != 0, "failed to open image file %s", filename.c_str());
        nc = width = height = 0;
        buffer = nullptr;
        return;
    }
    
    error_if_not(fscanf(f, "%s", identifier) == 1, "error reading image file");
    string id = identifier;
    
    if("Pf" == id) { nc = 1; ascii = false; type = 'f'; }
    else if ("PF"  == id) { nc = 3; ascii = false; type = 'f'; }
    else if ("P2"  == id) { nc = 1; ascii = true; type = 'B'; }
    else if ("P3"  == id) { nc = 3; ascii = true; type = 'B'; }
    else if ("P5"  == id) { nc = 1; ascii = false; type = 'B'; }
    else if ("P6"  == id) { nc = 3; ascii = false; type = 'B'; }
    else {	error("unknown image format"); }
    
    error_if_not(fscanf(f, "%d%d\n", &width, &height) == 2, "error reading image file %s", filename.c_str());
    if(type == 'B') { 
        buffer = new unsigned char[width*height*nc]; 
        int maxv;
        error_if_not(fscanf(f, "%d\n", &maxv) == 1, "error reading image file %s", filename.c_str());
        error_if_not(maxv == 255, "unsupported max value");
        scale = 1.0f / maxv;
        if(!ascii) {
            int bread = (int)fread(buffer, sizeof(unsigned char), width*height*nc, f);
            error_if_not(bread == width*height*nc, "error reading image file %s", filename.c_str());
        } else {
            unsigned char* buf = (unsigned char*)buffer;
            for(int i = 0; i < width*height*nc; i ++) {
                int v;
                error_if_not(fscanf(f, "%d", &v) == 1, "error reading image file %s", filename.c_str());
                buf[i] = (unsigned char)v;
            }
        }
    } else if(type == 'f') { 
        buffer = (unsigned char*)(new float[width*height*nc]);
        char scale_string[16];
        error_if_not(fgets(scale_string, 16, f) == scale_string, "error reading with fgets, file %s", filename.c_str());
        sscanf(scale_string, "%f\n", &scale);
        error_if_not(scale < 0, "only support little endian pfm");
        scale = abs(scale);
        // flip y while reading
        for(int j = height-1; j >= 0; j --) {
            float* buf = (float*)buffer;
            int bytes = (int)fread(buf + (j*width*nc), sizeof(float), width*nc, f);
            error_if_not(bytes == width*nc, "error reading image file");
        }
    } else error("unknown type");
    
    fclose(f);
}
예제 #4
0
// main function
int main(int argc, char** argv) {
    auto args = parse_cmdline(argc, argv,
        { "03_animate", "view scene",
            {  {"resolution", "r", "image resolution", typeid(int), true, jsonvalue() }  },
            {  {"scene_filename", "", "scene filename", typeid(string), false, jsonvalue("scene.json")},
               {"image_filename", "", "image filename", typeid(string), true, jsonvalue("")}  }
        });
    
    // generate/load scene either by creating a test scene or loading from json file
    scene_filename = args.object_element("scene_filename").as_string();
    scene = nullptr;
    if(scene_filename.length() > 9 and scene_filename.substr(0,9) == "testscene") {
        int scene_type = atoi(scene_filename.substr(9).c_str());
        scene = create_test_scene(scene_type);
        scene_filename = scene_filename + ".json";
    } else {
        scene = load_json_scene(scene_filename);
    }
    error_if_not(scene, "scene is nullptr");
    
    image_filename = (args.object_element("image_filename").as_string() != "") ?
        args.object_element("image_filename").as_string() :
        scene_filename.substr(0,scene_filename.size()-5)+".png";
    
    if(not args.object_element("resolution").is_null()) {
        scene->image_height = args.object_element("resolution").as_int();
        scene->image_width = scene->camera->width * scene->image_height / scene->camera->height;
    }
    
    animate_reset(scene);
    
    subdivide(scene);
    
    uiloop();
}
예제 #5
0
CatmullClarkSubdiv* mesh_to_catmullclark(Mesh* mesh) {
    error_if_not(mesh->triangle.empty(), "mesh cannot be converted to catmullclark: has triangles");
    auto subdiv = new CatmullClarkSubdiv();
    subdiv->pos = mesh->pos;
    subdiv->texcoord = mesh->texcoord;
    subdiv->quad = mesh->quad;
    subdiv->_tesselation_lines = mesh->_tesselation_lines;
    return subdiv;
}
예제 #6
0
TriangleMesh* mesh_to_trianglemesh(Mesh* mesh) {
    error_if_not(mesh->quad.empty(), "mesh cannot be converted to trianglemesh: has quads");
    auto trianglemesh = new TriangleMesh();
    trianglemesh->pos = mesh->pos;
    trianglemesh->norm = mesh->norm;
    trianglemesh->texcoord = mesh->texcoord;
    trianglemesh->triangle = mesh->triangle;
    trianglemesh->_tesselation_lines = mesh->_tesselation_lines;
    return trianglemesh;
}
예제 #7
0
// intersects the scene and return for any intersection
bool intersect_shadow(Scene* scene, ray3f ray) {
    // foreach surface
    for(auto surface : scene->surfaces) {
        // if it is a quad
        if(surface->isquad) {
            // compute ray intersection (and ray parameter), continue if not hit
            auto tray = transform_ray_inverse(surface->frame,ray);
            
            // intersect quad
            if(intersect_quad(tray, surface->radius)) return true;
        } else {
            // compute ray intersection (and ray parameter), continue if not hit
            auto tray = transform_ray_inverse(surface->frame,ray);
            
            // intersect sphere
            if(intersect_sphere(tray, surface->radius)) return true;
        }
    }
    // foreach mesh
    for(auto mesh : scene->meshes) {
        // quads are not supported: check for error
        error_if_not(mesh->quad.empty(), "quad intersection is not supported");
        // tranform the ray
        auto tray = transform_ray_inverse(mesh->frame, ray);
        // if it is accelerated
        if(mesh->bvh) {
            if(intersect_shadow(mesh->bvh, 0, tray,
                                [mesh](int tid, ray3f tray){
                                    // grab triangle
                                    auto triangle = mesh->triangle[tid];
                                          
                                    // grab vertices
                                    auto v0 = mesh->pos[triangle.x];
                                    auto v1 = mesh->pos[triangle.y];
                                    auto v2 = mesh->pos[triangle.z];
                            
                                    // return if intersected
                                    return intersect_triangle(tray, v0, v1, v2);})) return true;
        } else {
            // foreach triangle
            for(auto triangle : mesh->triangle) {
                // grab vertices
                auto v0 = mesh->pos[triangle.x];
                auto v1 = mesh->pos[triangle.y];
                auto v2 = mesh->pos[triangle.z];
                
                // intersect triangle
                if(intersect_triangle(tray, v0, v1, v2)) return true;
            }
        }
    }
    
    // no intersection found
    return false;
}
예제 #8
0
image3f read_png(const string& filename, bool flipY) {
    vector<unsigned char> pixels;
    unsigned width, height;
    
    unsigned error = lodepng::decode(pixels, width, height, filename);
    error_if_not(not error,"cannot read png image: %s", filename.c_str());
    
    error_if_not(pixels.size() == width*height*4, "bad reading");
    
    image3f img(width,height);
    for(int i = 0; i < width*height; i ++) {
        int x = i%width;
        int y = i/width;
        int ii = (flipY) ? x + (height-y-1)*width : x + y*width;
        img.data()[ii].x = pixels[i*4+0] / 255.0f;
        img.data()[ii].y = pixels[i*4+1] / 255.0f;
        img.data()[ii].z = pixels[i*4+2] / 255.0f;
    }
    
    return img;
}
예제 #9
0
void write_png(const string& filename, const image3f& img, bool flipY) {
    vector<unsigned char> img_png(img.width()*img.height()*4);
    for(int x = 0; x < img.width(); x++ ) {
        for( int y = 0; y < img.height(); y++ ) {
            int i_img = y * img.width() + x;
            int i_png = ( ( flipY ? (img.height()-1-y) : y ) * img.width() + x ) * 4;
            img_png[i_png+0] = (unsigned char)clamp(img.data()[i_img].x * 255, 0.0f, 255.0f);
            img_png[i_png+1] = (unsigned char)clamp(img.data()[i_img].y * 255, 0.0f, 255.0f);
            img_png[i_png+2] = (unsigned char)clamp(img.data()[i_img].z * 255, 0.0f, 255.0f);
            img_png[i_png+3] = 255;
        }
    }
    unsigned error = lodepng::encode(filename, img_png, img.width(), img.height());
    error_if_not(not error, "cannot write png image: %s", filename.c_str());
}
예제 #10
0
static void _write_pnm(const char *filename, char type,
                         int width, int height, int nc,
                         bool ascii, unsigned char* buffer) {
    FILE *f = fopen(filename, "wb");
    error_if_not(f != 0, "failed to create image file %s", filename);
    
    string magic; 
    int scale = -1; 
    int ds = 0;
    if(type == 'f' && nc == 1 && !ascii) { magic = "Pf"; scale = -1; 
        ds = sizeof(float); }
    else if(type == 'f' && nc == 3 && !ascii) { magic = "PF"; scale = -1; 
        ds = sizeof(float); }
    else if(type == 'B' && nc == 1 && !ascii) { magic = "P5"; scale = 255; 
        ds = sizeof(unsigned char); }
    else if(type == 'B' && nc == 3 && !ascii) { magic = "P6"; scale = 255; 
        ds = sizeof(unsigned char); }
    else if(type == 'B' && nc == 1 && ascii) { magic = "P2"; scale = 255; 
        ds = sizeof(unsigned char); }
    else if(type == 'B' && nc == 3 && ascii) { magic = "P3"; scale = 255; 
        ds = sizeof(unsigned char); }
    else error("unsupported image format");
    
    error_if_not(fprintf(f, "%s\n", magic.c_str()) > 0, "error writing file %s", filename);
    error_if_not(fprintf(f, "%d %d\n", width, height) > 0, "error writing file %s", filename);
    error_if_not(fprintf(f, "%d\n", scale) > 0, "error writing file %s", filename);
    
    if(!ascii) {
        if(type == 'f') {
            for(int j = height-1; j >= 0; j --) {
                float* buf = (float*)buffer;
                error_if_not((int)fwrite(buf + j*width*nc, ds, width*nc, f) == width*nc, "error writing file %s", filename);
            }
        } else {
            error_if_not((int)fwrite(buffer, ds, width*height*nc, f) == width*height*nc, "error writing file %s", filename);
        }
    } else {
        unsigned char* buf = (unsigned char*)buffer;
        for(int i = 0; i < width*height*nc; i ++) {
            int v = buf[i];
            error_if_not(fprintf(f, "%d \n", v) != 0, "error writing file %s", filename);
        }
    }
    
	fclose(f);
}
예제 #11
0
파일: scene.cpp 프로젝트: okate/Raytrace
void json_set_values(const jsonvalue& json, int* value, int n) {
    error_if_not(n == json.array_size(), "incorrect array size");
    for(auto i : range(n)) value[i] = json.array_element(i).as_int();
}
예제 #12
0
파일: model.cpp 프로젝트: istinj/HW03_srcs
// uiloop
void uiloop() {
    auto ok = glfwInit();
    error_if_not(ok, "glfw init error");
    
    // setting an error callback
    glfwSetErrorCallback([](int ecode, const char* msg){ return error(msg); });
    
    // glfwWindowHint(GLFW_SAMPLES, scene->image_samples*scene->image_samples);

    auto window = glfwCreateWindow(scene->image_width,
                                   scene->image_height,
                                   "graphics14 | model", NULL, NULL);
    error_if_not(window, "glfw window error");
    
    glfwMakeContextCurrent(window);
    
    glfwSetCharCallback(window, [](GLFWwindow* window, unsigned int key) {
        switch (key) {
            case 's':
                scene->draw_captureimage = true;
                break;
            case 'w':
                scene->draw_wireframe = ! scene->draw_wireframe;
                break;
        }
    });
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
    
#ifdef _WIN32
	auto ok1 = glewInit();
	error_if_not(GLEW_OK == ok1, "glew init error");
#endif

    auto state = new ShadeState();
    init_shaders(state);
    init_textures(scene,state);
    
    auto mouse_last_x = -1.0;
    auto mouse_last_y = -1.0;
    
    while(! glfwWindowShouldClose(window)) {
        glfwGetFramebufferSize(window, &scene->image_width, &scene->image_height);
        scene->camera->width = (scene->camera->height * scene->image_width) / scene->image_height;
        
        shade(scene,state);

        if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT)) {
            double x, y;
            glfwGetCursorPos(window, &x, &y);
            if (mouse_last_x < 0 || mouse_last_y < 0) { mouse_last_x = x; mouse_last_y = y; }
            auto delta_x = x - mouse_last_x, delta_y = y - mouse_last_y;
            
            set_view_turntable(scene->camera, delta_x*0.01, -delta_y*0.01, 0, 0, 0);
            
            mouse_last_x = x;
            mouse_last_y = y;
        } else { mouse_last_x = -1; mouse_last_y = -1; }
        
        if(scene->draw_captureimage) {
            auto image = image3f(scene->image_width,scene->image_height);
            glReadPixels(0, 0, scene->image_width, scene->image_height, GL_RGB, GL_FLOAT, &image.at(0,0));
            write_png(image_filename, image, true);
            scene->draw_captureimage = false;
        }
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    
    glfwTerminate();
    
    delete state;
}
예제 #13
0
// intersects the scene and return the first intrerseciton
intersection3f intersect(Scene* scene, ray3f ray) {
    // create a default intersection record to be returned
    auto intersection = intersection3f();
    // foreach surface
    for(auto surface : scene->surfaces) {
        // if it is a quad
        if(surface->isquad) {
            // compute ray intersection (and ray parameter), continue if not hit
            auto tray = transform_ray_inverse(surface->frame,ray);
            
            // intersect quad
            auto t = 0.0f; auto p = zero3f;
            auto hit = intersect_quad(tray, surface->radius, t, p);
            
            // skip if not hit
            if(not hit) continue;
            
            // check if this is the closest intersection, continue if not
            if(t > intersection.ray_t and intersection.hit) continue;
            
            // if hit, set intersection record values
            intersection.hit = true;
            intersection.ray_t = t;
            intersection.pos = transform_point(surface->frame,p);
            intersection.norm = transform_normal(surface->frame,z3f);
            intersection.texcoord = {0.5f*p.x/surface->radius+0.5f,0.5f*p.y/surface->radius+0.5f};
            intersection.mat = surface->mat;
        } else {
            // compute ray intersection (and ray parameter), continue if not hit
            auto tray = transform_ray_inverse(surface->frame,ray);
            
            // intersect sphere
            auto t = 0.0f;
            auto hit = intersect_sphere(tray, surface->radius, t);
            
            // skip if not hit
            if(not hit) continue;
            
            // check if this is the closest intersection, continue if not
            if(t > intersection.ray_t and intersection.hit) continue;
            
            // compute local point and normal
            auto p = tray.eval(t);
            auto n = normalize(p);
            
            // if hit, set intersection record values
            intersection.hit = true;
            intersection.ray_t = t;
            intersection.pos = transform_point(surface->frame,p);
            intersection.norm = transform_normal(surface->frame,n);
            intersection.texcoord = {(pif+(float)atan2(n.y, n.x))/(2*pif),(float)acos(n.z)/pif};
            intersection.mat = surface->mat;
        }
    }
    // foreach mesh
    for(auto mesh : scene->meshes) {
        // quads are not supported: check for error
        error_if_not(mesh->quad.empty(), "quad intersection is not supported");
        // tranform the ray
        auto tray = transform_ray_inverse(mesh->frame, ray);
        // save auto mesh intersection
        auto sintersection = intersection3f();
        // if it is accelerated
        if(mesh->bvh) {
            sintersection = intersect(mesh->bvh, 0, tray,
               [mesh](int tid, ray3f tray){
                   // grab triangle
                   auto triangle = mesh->triangle[tid];
                   
                   // grab vertices
                   auto v0 = mesh->pos[triangle.x];
                   auto v1 = mesh->pos[triangle.y];
                   auto v2 = mesh->pos[triangle.z];
                   
                   // intersect triangle
                   auto t = 0.0f, u = 0.0f, v = 0.0f;
                   auto hit = intersect_triangle(tray, v0, v1, v2, t, u, v);
                   
                   // skip if not hit
                   if(not hit) return intersection3f();
                   
                   // if hit, set up intersection, trasforming hit data to world space
                   auto sintersection = intersection3f();
                   sintersection.hit = true;
                   sintersection.ray_t = t;
                   sintersection.pos = tray.eval(t);
                   sintersection.norm = normalize(mesh->norm[triangle.x]*u+
                                                  mesh->norm[triangle.y]*v+
                                                  mesh->norm[triangle.z]*(1-u-v));
                   if(mesh->texcoord.empty()) sintersection.texcoord = zero2f;
                   else {
                       sintersection.texcoord = mesh->texcoord[triangle.x]*u+
                                                mesh->texcoord[triangle.y]*v+
                                                mesh->texcoord[triangle.z]*(1-u-v);
                   }
                   sintersection.mat = mesh->mat;
                   return sintersection;
               });
        } else {
            // clear intersection
            sintersection = intersection3f();
            // foreach triangle
            for(auto triangle : mesh->triangle) {
                // grab vertices
                auto v0 = mesh->pos[triangle.x];
                auto v1 = mesh->pos[triangle.y];
                auto v2 = mesh->pos[triangle.z];
                
                // intersect triangle
                auto t = 0.0f, u = 0.0f, v = 0.0f;
                auto hit = intersect_triangle(tray, v0, v1, v2, t, u, v);
                
                // skip if not hit
                if(not hit) continue;
                
                // check if closer then the found hit
                if(t > sintersection.ray_t and sintersection.hit) continue;
                
                // if hit, set up intersection, trasforming hit data to world space
                sintersection.hit = true;
                sintersection.ray_t = t;
                sintersection.pos = tray.eval(t);
                sintersection.norm = normalize(mesh->norm[triangle.x]*u+
                                               mesh->norm[triangle.y]*v+
                                               mesh->norm[triangle.z]*(1-u-v));
                if(mesh->texcoord.empty()) sintersection.texcoord = zero2f;
                else {
                    sintersection.texcoord = mesh->texcoord[triangle.x]*u+
                                             mesh->texcoord[triangle.y]*v+
                                             mesh->texcoord[triangle.z]*(1-u-v);
                }
                sintersection.mat = mesh->mat;
            }
        }
        // if did not hit the mesh, skip
        if(not sintersection.hit) continue;
        // check not first intersection, skip
        if(sintersection.ray_t > intersection.ray_t and intersection.hit) continue;
        // set interserction
        intersection = sintersection;
        // transform by mesh frame
        intersection.pos = transform_point(mesh->frame,sintersection.pos);
        intersection.norm = transform_normal(mesh->frame,sintersection.norm);
        // set material
        intersection.mat = sintersection.mat;
    }
    
    // record closest intersection
    return intersection;
}
예제 #14
0
파일: pam.cpp 프로젝트: ouj/jview
void loadPnm(const string& filename, rawtype& type,
             int& width, int& height, int& nc,
             float& scale, rawbuffer& buffer) {
	char identifier[4];
    bool ascii = false;

	FILE *f = fopen(filename.c_str(), "rb");
    if (f == 0) {
        error_if_not_va(f != 0, "failed to open image file %s", filename.c_str());
        nc = width = height = 0;
        buffer = 0;
        return;
    }

	error_if_not(fscanf(f, "%s", identifier) == 1, "error reading image file");
	string id = identifier;

	if("Pf" == id) { nc = 1; ascii = false; type = 'f'; }
	else if ("PF"  == id) { nc = 3; ascii = false; type = 'f'; }
	else if ("P2"  == id) { nc = 1; ascii = true; type = 'B'; }
	else if ("P3"  == id) { nc = 3; ascii = true; type = 'B'; }
	else if ("P5"  == id) { nc = 1; ascii = false; type = 'B'; }
	else if ("P6"  == id) { nc = 3; ascii = false; type = 'B'; }
	else {	error("unknown image format"); }

	error_if_not_va(fscanf(f, "%d%d\n", &width, &height) == 2,
                    "error reading image file %s, width: %d, height: %d",
                    filename.c_str(), width, height);
    if(type == 'B') {
        buffer = (rawbuffer)(new unsigned char[width*height*nc]);
        int maxv;
        error_if_not_va(fscanf(f, "%d", &maxv) == 1, "error reading image file %s", filename.c_str());
        getc(f); //each the \n character.
        error_if_not(maxv == 255, "unsupported max value");
        scale = 1.0f / maxv;
        if(!ascii) {
            int bread = (int)fread(buffer, sizeof(unsigned char), width*height*nc, f);
            warning_if_not_va(bread == width*height*nc,
                            "error reading image file %s, loaded(%d), expected(%d)",
                            filename.c_str(), bread, width*height*nc);
        } else {
            unsigned char* buf = (unsigned char*)buffer;
            for(int i = 0; i < width*height*nc; i ++) {
                int v;
                error_if_not_va(fscanf(f, "%d", &v) == 1, "error reading image file %s", filename.c_str());
                buf[i] = (unsigned char)v;
            }
        }
    } else if(type == 'f') {
        buffer = (rawbuffer)(new float[width*height*nc]);
        char scale_string[16];
        error_if_not(fgets(scale_string, 16, f), "error reading scale string");
        sscanf(scale_string, "%f\n", &scale);
        scale = abs(scale);
        // flip y while reading
        for(int j = height-1; j >= 0; j --) {
            float* buf = (float*)buffer;
            int bytes = (int)fread(buf + (j*width*nc), sizeof(float), width*nc, f);
            error_if_not(bytes == width*nc, "error reading image file");
        }
    } else error("unknown type");

    fclose(f);
}
예제 #15
0
// uiloop
void uiloop() {
    
    auto ok_glfw = glfwInit();
    error_if_not(ok_glfw, "glfw init error");
    
    // setting an error callback
    glfwSetErrorCallback([](int ecode, const char* msg){ return error(msg); });
    
    glfwWindowHint(GLFW_SAMPLES, scene->image_samples);

    auto window = glfwCreateWindow(scene->image_width, scene->image_height,
                                   "graphics | animate", NULL, NULL);
    error_if_not(window, "glfw window error");
    
    glfwMakeContextCurrent(window);
    
    glfwSetCharCallback(window, [](GLFWwindow* window, unsigned int key) {
        switch (key) {
            case 's': { save = true; } break;
            case ' ': { animate = not animate; } break;
            case '.': { animate_update(scene, skinning_gpu); } break;
            case 'g': { skinning_gpu = not skinning_gpu; animate_reset(scene); } break;
            case 'n': { draw_normals = not draw_normals; } break;
            case 'e': { draw_edges = not draw_edges; } break;
            case 'p': { draw_points = not draw_points; } break;
            case 'f': { draw_faces = not draw_faces; } break;
        }
    });
    
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
    
    auto ok_glew = glewInit();
    error_if_not(GLEW_OK == ok_glew, "glew init error");
    
    init_shaders();
    init_textures(scene);
    animate_reset(scene);
    
    auto mouse_last_x = -1.0;
    auto mouse_last_y = -1.0;
    
    auto last_update_time = glfwGetTime();
    
    while(not glfwWindowShouldClose(window)) {
        auto title = tostring("graphics | animate | %03d", scene->animation->time);
        glfwSetWindowTitle(window, title.c_str());
        
        if(animate) {
            if(glfwGetTime() - last_update_time > scene->animation->dt) {
                last_update_time = glfwGetTime();
                animate_update(scene, skinning_gpu);
            }
        }
        
        if(save) {
            animate_reset(scene);
            for(auto i : range(scene->animation->length/3)) animate_update(scene, skinning_gpu);
        }
        
        glfwGetFramebufferSize(window, &scene->image_width, &scene->image_height);
        scene->camera->width = (scene->camera->height * scene->image_width) / scene->image_height;
        
        shade(scene);

        if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT)) {
            double x, y;
            glfwGetCursorPos(window, &x, &y);
            if (mouse_last_x < 0 or mouse_last_y < 0) { mouse_last_x = x; mouse_last_y = y; }
            auto delta_x = x - mouse_last_x, delta_y = y - mouse_last_y;
            
            set_view_turntable(scene->camera, delta_x*0.01, -delta_y*0.01, 0, 0, 0);
            
            mouse_last_x = x;
            mouse_last_y = y;
        } else { mouse_last_x = -1; mouse_last_y = -1; }
        
        if(save) {
            auto image = image3f(scene->image_width,scene->image_height);
            glReadPixels(0, 0, scene->image_width, scene->image_height, GL_RGB, GL_FLOAT, &image.at(0,0));
            write_png(image_filename, image, true);
            save = false;
        }
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    
    glfwTerminate();
}