// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2014 Daniele Panozzo // // 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 "ViewerData.h" #include "gl.h" #include "../quat_to_mat.h" #include "../snap_to_fixed_up.h" #include "../look_at.h" #include "../frustum.h" #include "../ortho.h" #include "../massmatrix.h" #include "../barycenter.h" #include "../PI.h" #include #include 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,camera_base_zoom,camera_base_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,camera_base_zoom,camera_base_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(); zoom = 2.0 / (max_point-min_point).array().abs().maxCoeff(); } IGL_INLINE void igl::opengl::ViewerCore::clear_framebuffers() { // The glScissor call ensures we only clear this core's buffers, // (in case the user wants different background colors in each viewport.) glScissor(viewport(0), viewport(1), viewport(2), viewport(3)); glEnable(GL_SCISSOR_TEST); glClearColor(background_color[0], background_color[1], background_color[2], background_color[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } IGL_INLINE void igl::opengl::ViewerCore::draw( ViewerData& data, 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) { data.updateGL(data, data.invert_normals, data.meshgl); data.dirty = MeshGL::DIRTY_NONE; } data.meshgl.bind_mesh(); // Initialize uniform glViewport(viewport(0), viewport(1), viewport(2), viewport(3)); if(update_matrices) { view = Eigen::Matrix4f::Identity(); proj = Eigen::Matrix4f::Identity(); norm = Eigen::Matrix4f::Identity(); float width = viewport(2); float height = viewport(3); // Set view look_at( camera_eye, camera_center, camera_up, view); view = view * (trackball_angle * Eigen::Scaling(camera_zoom * camera_base_zoom) * Eigen::Translation3f(camera_translation + camera_base_translation)).matrix(); norm = view.inverse().transpose(); // Set projection if (orthographic) { float length = (camera_eye - camera_center).norm(); float h = tan(camera_view_angle/360.0 * igl::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 * igl::PI) * camera_dnear; float fW = fH * (double)width/(double)height; frustum(-fW, fW, -fH, fH, camera_dnear, camera_dfar,proj); } } // Send transformations to the GPU GLint viewi = glGetUniformLocation(data.meshgl.shader_mesh,"view"); GLint proji = glGetUniformLocation(data.meshgl.shader_mesh,"proj"); GLint normi = glGetUniformLocation(data.meshgl.shader_mesh,"normal_matrix"); glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); glUniformMatrix4fv(normi, 1, GL_FALSE, norm.data()); // Light parameters GLint specular_exponenti = glGetUniformLocation(data.meshgl.shader_mesh,"specular_exponent"); GLint light_position_eyei = glGetUniformLocation(data.meshgl.shader_mesh,"light_position_eye"); GLint lighting_factori = glGetUniformLocation(data.meshgl.shader_mesh,"lighting_factor"); GLint fixed_colori = glGetUniformLocation(data.meshgl.shader_mesh,"fixed_color"); GLint texture_factori = glGetUniformLocation(data.meshgl.shader_mesh,"texture_factor"); GLint matcap_factori = glGetUniformLocation(data.meshgl.shader_mesh,"matcap_factor"); GLint double_sidedi = glGetUniformLocation(data.meshgl.shader_mesh,"double_sided"); glUniform1f(specular_exponenti, data.shininess); glUniform3fv(light_position_eyei, 1, light_position.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 (is_set(data.show_faces)) { // Texture glUniform1f(texture_factori, is_set(data.show_texture) ? 1.0f : 0.0f); glUniform1f(matcap_factori, is_set(data.use_matcap) ? 1.0f : 0.0f); glUniform1f(double_sidedi, data.double_sided ? 1.0f : 0.0f); data.meshgl.draw_mesh(true); glUniform1f(matcap_factori, 0.0f); glUniform1f(texture_factori, 0.0f); } // Render wireframe if (is_set(data.show_lines)) { glLineWidth(data.line_width); glUniform4f(fixed_colori, data.line_color[0], data.line_color[1], data.line_color[2], data.line_color[3]); data.meshgl.draw_mesh(false); glUniform4f(fixed_colori, 0.0f, 0.0f, 0.0f, 0.0f); } } if (is_set(data.show_overlay)) { if (is_set(data.show_overlay_depth)) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (data.lines.rows() > 0) { data.meshgl.bind_overlay_lines(); viewi = glGetUniformLocation(data.meshgl.shader_overlay_lines,"view"); proji = glGetUniformLocation(data.meshgl.shader_overlay_lines,"proj"); 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(data.line_width); data.meshgl.draw_overlay_lines(); } if (data.points.rows() > 0) { data.meshgl.bind_overlay_points(); viewi = glGetUniformLocation(data.meshgl.shader_overlay_points,"view"); proji = glGetUniformLocation(data.meshgl.shader_overlay_points,"proj"); glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); glPointSize(data.point_size); data.meshgl.draw_overlay_points(); } glEnable(GL_DEPTH_TEST); } if(is_set(data.show_vertex_labels)&&data.vertex_labels_positions.rows()>0) draw_labels(data, data.meshgl.vertex_labels); if(is_set(data.show_face_labels)&&data.face_labels_positions.rows()>0) draw_labels(data, data.meshgl.face_labels); if(is_set(data.show_custom_labels)&&data.labels_positions.rows()>0) draw_labels(data, data.meshgl.custom_labels); } IGL_INLINE void igl::opengl::ViewerCore::draw_buffer( ViewerData& data, bool update_matrices, Eigen::Matrix& R, Eigen::Matrix& G, Eigen::Matrix& B, Eigen::Matrix& 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 width = R.rows(); unsigned height = R.cols(); if(width == 0 && height == 0) { width = viewport(2); height = viewport(3); } R.resize(width,height); G.resize(width,height); B.resize(width,height); A.resize(width,height); //////////////////////////////////////////////////////////////////////// // PREPARE width×height BUFFERS does *not* depend on `data` // framebuffer // textureColorBufferMultiSampled // rbo // intermediateFBO // screenTexture // //////////////////////////////////////////////////////////////////////// // https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing // Create an initial multisampled framebuffer unsigned int framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); // create a multisampled color attachment texture unsigned int textureColorBufferMultiSampled; glGenTextures(1, &textureColorBufferMultiSampled); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, width, height, GL_TRUE); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled, 0); // create a (also multisampled) renderbuffer object for depth and stencil attachments unsigned int rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); glBindFramebuffer(GL_FRAMEBUFFER, 0); // configure second post-processing framebuffer unsigned int intermediateFBO; glGenFramebuffers(1, &intermediateFBO); glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO); // create a color attachment texture unsigned int screenTexture; glGenTextures(1, &screenTexture); glBindTexture(GL_TEXTURE_2D, screenTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 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, screenTexture, 0); // we only need a color buffer assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); glBindFramebuffer(GL_FRAMEBUFFER, 0); 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,width,height; // Draw draw(data,update_matrices); // Restore viewport viewport = viewport_ori; glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO); // Copy back in the given Eigen matrices GLubyte* pixels = (GLubyte*)calloc(width*height*4,sizeof(GLubyte)); glReadPixels(0, 0,width, height,GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Clean up glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteTextures(1, &screenTexture); glDeleteTextures(1, &textureColorBufferMultiSampled); glDeleteFramebuffers(1, &framebuffer); glDeleteFramebuffers(1, &intermediateFBO); glDeleteRenderbuffers(1, &rbo); int count = 0; for (unsigned j=0; j