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; }
// 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(); }
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); }
// 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(); }
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; }
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; }
// 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; }
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; }
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()); }
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); }
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(); }
// 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; }
// 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; }
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); }
// 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(); }