You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
16 KiB
479 lines
16 KiB
// 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 "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 <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,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<float>();
|
|
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<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 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<height; ++j)
|
|
{
|
|
for (unsigned i=0; i<width; ++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);
|
|
}
|
|
|
|
// Define uniforms for text labels
|
|
IGL_INLINE void igl::opengl::ViewerCore::draw_labels(
|
|
ViewerData& data,
|
|
const igl::opengl::MeshGL::TextGL& labels
|
|
){
|
|
glDisable(GL_LINE_SMOOTH); // Clear settings if overlay is activated
|
|
data.meshgl.bind_labels(labels);
|
|
GLint viewi = glGetUniformLocation(data.meshgl.shader_text,"view");
|
|
GLint proji = glGetUniformLocation(data.meshgl.shader_text,"proj");
|
|
glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data());
|
|
glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data());
|
|
// Parameters for mapping characters from font atlass
|
|
float width = viewport(2);
|
|
float height = viewport(3);
|
|
float text_shift_scale_factor = orthographic ? 0.01 : 0.03;
|
|
float render_scale = (orthographic ? 0.6 : 1.7) * data.label_size;
|
|
glUniform1f(glGetUniformLocation(data.meshgl.shader_text, "TextShiftFactor"), text_shift_scale_factor);
|
|
glUniform3f(glGetUniformLocation(data.meshgl.shader_text, "TextColor"), data.label_color(0), data.label_color(1), data.label_color(2));
|
|
glUniform2f(glGetUniformLocation(data.meshgl.shader_text, "CellSize"), 1.0f / 16, (300.0f / 384) / 6);
|
|
glUniform2f(glGetUniformLocation(data.meshgl.shader_text, "CellOffset"), 0.5 / 256.0, 0.5 / 256.0);
|
|
glUniform2f(glGetUniformLocation(data.meshgl.shader_text, "RenderSize"),
|
|
render_scale * 0.75 * 16 / (width),
|
|
render_scale * 0.75 * 33.33 / (height));
|
|
glUniform2f(glGetUniformLocation(data.meshgl.shader_text, "RenderOrigin"), -2, 2);
|
|
data.meshgl.draw_labels(labels);
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
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 void igl::opengl::ViewerCore::set(unsigned int &property_mask, bool value) const
|
|
{
|
|
if (!value)
|
|
unset(property_mask);
|
|
else
|
|
property_mask |= id;
|
|
}
|
|
|
|
IGL_INLINE void igl::opengl::ViewerCore::unset(unsigned int &property_mask) const
|
|
{
|
|
property_mask &= ~id;
|
|
}
|
|
|
|
IGL_INLINE void igl::opengl::ViewerCore::toggle(unsigned int &property_mask) const
|
|
{
|
|
property_mask ^= id;
|
|
}
|
|
|
|
IGL_INLINE bool igl::opengl::ViewerCore::is_set(unsigned int property_mask) const
|
|
{
|
|
return (property_mask & id);
|
|
}
|
|
|
|
IGL_INLINE igl::opengl::ViewerCore::ViewerCore()
|
|
{
|
|
// Default colors
|
|
background_color << 0.3f, 0.3f, 0.5f, 1.0f;
|
|
|
|
// Default lights settings
|
|
light_position << 0.0f, 0.3f, 0.0f;
|
|
lighting_factor = 1.0f; //on
|
|
|
|
// Default trackball
|
|
trackball_angle = Eigen::Quaternionf::Identity();
|
|
rotation_type = ViewerCore::ROTATION_TYPE_TRACKBALL;
|
|
set_rotation_type(ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP);
|
|
|
|
// Camera parameters
|
|
camera_base_zoom = 1.0f;
|
|
camera_zoom = 1.0f;
|
|
orthographic = false;
|
|
camera_view_angle = 45.0;
|
|
camera_dnear = 1.0;
|
|
camera_dfar = 100.0;
|
|
camera_base_translation << 0, 0, 0;
|
|
camera_translation << 0, 0, 0;
|
|
camera_eye << 0, 0, 5;
|
|
camera_center << 0, 0, 0;
|
|
camera_up << 0, 1, 0;
|
|
|
|
depth_test = true;
|
|
|
|
is_animating = false;
|
|
animation_max_fps = 30.;
|
|
|
|
viewport.setZero();
|
|
}
|
|
|
|
IGL_INLINE void igl::opengl::ViewerCore::init()
|
|
{
|
|
}
|
|
|
|
IGL_INLINE void igl::opengl::ViewerCore::shut()
|
|
{
|
|
}
|
|
|