// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com> // // This Source Code Form is subject to the terms of the Mozilla Public License // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #include "ViewerCore.h" #include <igl/quat_to_mat.h> #include <igl/snap_to_fixed_up.h> #include <igl/look_at.h> #include <igl/frustum.h> #include <igl/ortho.h> #include <igl/massmatrix.h> #include <igl/barycenter.h> #include <Eigen/Geometry> #include <iostream> IGL_INLINE void igl::opengl::ViewerCore::align_camera_center( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) { if(V.rows() == 0) return; get_scale_and_shift_to_fit_mesh(V,F,model_zoom,model_translation); // Rather than crash on empty mesh... if(V.size() > 0) { object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm(); } } IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, float& zoom, Eigen::Vector3f& shift) { if (V.rows() == 0) return; Eigen::MatrixXd BC; if (F.rows() <= 1) { BC = V; } else { igl::barycenter(V,F,BC); } return get_scale_and_shift_to_fit_mesh(BC,zoom,shift); } IGL_INLINE void igl::opengl::ViewerCore::align_camera_center( const Eigen::MatrixXd& V) { if(V.rows() == 0) return; get_scale_and_shift_to_fit_mesh(V,model_zoom,model_translation); // Rather than crash on empty mesh... if(V.size() > 0) { object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm(); } } IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh( const Eigen::MatrixXd& V, float& zoom, Eigen::Vector3f& shift) { if (V.rows() == 0) return; auto min_point = V.colwise().minCoeff(); auto max_point = V.colwise().maxCoeff(); auto centroid = (0.5*(min_point + max_point)).eval(); shift.setConstant(0); shift.head(centroid.size()) = -centroid.cast<float>(); zoom = 2.0 / (max_point-min_point).array().abs().maxCoeff(); } IGL_INLINE void igl::opengl::ViewerCore::clear_framebuffers() { glClearColor(background_color[0], background_color[1], background_color[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } IGL_INLINE void igl::opengl::ViewerCore::draw( ViewerData& data, State& opengl, bool update_matrices) { using namespace std; using namespace Eigen; if (depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* Bind and potentially refresh mesh/line/point data */ if (data.dirty) { opengl.set_data(data, invert_normals); data.dirty = ViewerData::DIRTY_NONE; } opengl.bind_mesh(); // Initialize uniform glViewport(viewport(0), viewport(1), viewport(2), viewport(3)); if(update_matrices) { model = Eigen::Matrix4f::Identity(); view = Eigen::Matrix4f::Identity(); proj = Eigen::Matrix4f::Identity(); // Set view look_at( camera_eye, camera_center, camera_up, view); float width = viewport(2); float height = viewport(3); // Set projection if (orthographic) { float length = (camera_eye - camera_center).norm(); float h = tan(camera_view_angle/360.0 * M_PI) * (length); ortho(-h*width/height, h*width/height, -h, h, camera_dnear, camera_dfar,proj); } else { float fH = tan(camera_view_angle / 360.0 * M_PI) * camera_dnear; float fW = fH * (double)width/(double)height; frustum(-fW, fW, -fH, fH, camera_dnear, camera_dfar,proj); } // end projection // Set model transformation float mat[16]; igl::quat_to_mat(trackball_angle.coeffs().data(), mat); for (unsigned i=0;i<4;++i) for (unsigned j=0;j<4;++j) model(i,j) = mat[i+4*j]; // Why not just use Eigen::Transform<double,3,Projective> for model...? model.topLeftCorner(3,3)*=camera_zoom; model.topLeftCorner(3,3)*=model_zoom; model.col(3).head(3) += model.topLeftCorner(3,3)*model_translation; } // Send transformations to the GPU GLint modeli = opengl.shader_mesh.uniform("model"); GLint viewi = opengl.shader_mesh.uniform("view"); GLint proji = opengl.shader_mesh.uniform("proj"); glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data()); glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); // Light parameters GLint specular_exponenti = opengl.shader_mesh.uniform("specular_exponent"); GLint light_position_worldi = opengl.shader_mesh.uniform("light_position_world"); GLint lighting_factori = opengl.shader_mesh.uniform("lighting_factor"); GLint fixed_colori = opengl.shader_mesh.uniform("fixed_color"); GLint texture_factori = opengl.shader_mesh.uniform("texture_factor"); glUniform1f(specular_exponenti, shininess); Vector3f rev_light = -1.*light_position; glUniform3fv(light_position_worldi, 1, rev_light.data()); glUniform1f(lighting_factori, lighting_factor); // enables lighting glUniform4f(fixed_colori, 0.0, 0.0, 0.0, 0.0); if (data.V.rows()>0) { // Render fill if (show_faces) { // Texture glUniform1f(texture_factori, show_texture ? 1.0f : 0.0f); opengl.draw_mesh(true); glUniform1f(texture_factori, 0.0f); } // Render wireframe if (show_lines) { glLineWidth(line_width); glUniform4f(fixed_colori, line_color[0], line_color[1], line_color[2], 1.0f); opengl.draw_mesh(false); glUniform4f(fixed_colori, 0.0f, 0.0f, 0.0f, 0.0f); } #ifdef IGL_VIEWER_WITH_NANOGUI if (show_vertid) { textrenderer.BeginDraw(view*model, proj, viewport, object_scale); for (int i=0; i<data.V.rows(); ++i) textrenderer.DrawText(data.V.row(i),data.V_normals.row(i),to_string(i)); textrenderer.EndDraw(); } if (show_faceid) { textrenderer.BeginDraw(view*model, proj, viewport, object_scale); for (int i=0; i<data.F.rows(); ++i) { Eigen::RowVector3d p = Eigen::RowVector3d::Zero(); for (int j=0;j<data.F.cols();++j) p += data.V.row(data.F(i,j)); p /= data.F.cols(); textrenderer.DrawText(p, data.F_normals.row(i), to_string(i)); } textrenderer.EndDraw(); } #endif } if (show_overlay) { if (show_overlay_depth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (data.lines.rows() > 0) { opengl.bind_overlay_lines(); modeli = opengl.shader_overlay_lines.uniform("model"); viewi = opengl.shader_overlay_lines.uniform("view"); proji = opengl.shader_overlay_lines.uniform("proj"); glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data()); glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); // This must be enabled, otherwise glLineWidth has no effect glEnable(GL_LINE_SMOOTH); glLineWidth(line_width); opengl.draw_overlay_lines(); } if (data.points.rows() > 0) { opengl.bind_overlay_points(); modeli = opengl.shader_overlay_points.uniform("model"); viewi = opengl.shader_overlay_points.uniform("view"); proji = opengl.shader_overlay_points.uniform("proj"); glUniformMatrix4fv(modeli, 1, GL_FALSE, model.data()); glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); glPointSize(point_size); opengl.draw_overlay_points(); } #ifdef IGL_VIEWER_WITH_NANOGUI if (data.labels_positions.rows() > 0) { textrenderer.BeginDraw(view*model, proj, viewport, object_scale); for (int i=0; i<data.labels_positions.rows(); ++i) textrenderer.DrawText(data.labels_positions.row(i), Eigen::Vector3d(0.0,0.0,0.0), data.labels_strings[i]); textrenderer.EndDraw(); } #endif glEnable(GL_DEPTH_TEST); } } IGL_INLINE void igl::opengl::ViewerCore::draw_buffer(ViewerData& data, State& opengl, bool update_matrices, Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& R, Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& G, Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& B, Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic>& A) { assert(R.rows() == G.rows() && G.rows() == B.rows() && B.rows() == A.rows()); assert(R.cols() == G.cols() && G.cols() == B.cols() && B.cols() == A.cols()); unsigned x = R.rows(); unsigned y = R.cols(); // Create frame buffer GLuint frameBuffer; glGenFramebuffers(1, &frameBuffer); glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); // Create texture to hold color buffer GLuint texColorBuffer; glGenTextures(1, &texColorBuffer); glBindTexture(GL_TEXTURE_2D, texColorBuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0); // Create Renderbuffer Object to hold depth and stencil buffers GLuint rboDepthStencil; glGenRenderbuffers(1, &rboDepthStencil); glBindRenderbuffer(GL_RENDERBUFFER, rboDepthStencil); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, x, y); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rboDepthStencil); assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); // Clear the buffer glClearColor(background_color(0), background_color(1), background_color(2), 0.f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Save old viewport Eigen::Vector4f viewport_ori = viewport; viewport << 0,0,x,y; // Draw draw(data,opengl,update_matrices); // Restore viewport viewport = viewport_ori; // Copy back in the given Eigen matrices GLubyte* pixels = (GLubyte*)calloc(x*y*4,sizeof(GLubyte)); glReadPixels ( 0, 0, x, y, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); int count = 0; for (unsigned j=0; j<y; ++j) { for (unsigned i=0; i<x; ++i) { R(i,j) = pixels[count*4+0]; G(i,j) = pixels[count*4+1]; B(i,j) = pixels[count*4+2]; A(i,j) = pixels[count*4+3]; ++count; } } // Clean up free(pixels); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteRenderbuffers(1, &rboDepthStencil); glDeleteTextures(1, &texColorBuffer); glDeleteFramebuffers(1, &frameBuffer); } IGL_INLINE void igl::opengl::ViewerCore::set_rotation_type( const igl::opengl::ViewerCore::RotationType & value) { using namespace Eigen; using namespace std; const RotationType old_rotation_type = rotation_type; rotation_type = value; if(rotation_type == ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP && old_rotation_type != ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP) { snap_to_fixed_up(Quaternionf(trackball_angle),trackball_angle); } } IGL_INLINE igl::opengl::ViewerCore::ViewerCore() { // Default shininess shininess = 35.0f; // Default colors background_color << 0.3f, 0.3f, 0.5f, 1.0f; line_color << 0.0f, 0.0f, 0.0f, 1.0f; // Default lights settings light_position << 0.0f, -0.30f, -5.0f; lighting_factor = 1.0f; //on // Default trackball trackball_angle = Eigen::Quaternionf::Identity(); set_rotation_type(ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP); // Defalut model viewing parameters model_zoom = 1.0f; model_translation << 0,0,0; // Camera parameters camera_zoom = 1.0f; orthographic = false; camera_view_angle = 45.0; camera_dnear = 1.0; camera_dfar = 100.0; camera_eye << 0, 0, 5; camera_center << 0, 0, 0; camera_up << 0, 1, 0; // Default visualization options show_faces = true; show_lines = true; invert_normals = false; show_overlay = true; show_overlay_depth = true; show_vertid = false; show_faceid = false; show_texture = false; depth_test = true; // Default point size / line width point_size = 30; line_width = 0.5f; is_animating = false; animation_max_fps = 30.; } IGL_INLINE void igl::opengl::ViewerCore::init() { #ifdef IGL_VIEWER_WITH_NANOGUI textrenderer.Init(); #endif } IGL_INLINE void igl::opengl::ViewerCore::shut() { #ifdef IGL_VIEWER_WITH_NANOGUI textrenderer.Shut(); #endif }