1 // Copyright 2016-2021 Doug Moen
2 // Licensed under the Apache License, version 2.0
3 // See accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0
4 
5 // Based on glslViewer, which is:
6 // Copyright 2014 Patricio Gonzalez Vivo
7 // Licensed under the 3-Clause BSD Licence:
8 // https://opensource.org/licenses/BSD-3-Clause
9 
10 #include <libcurv/viewer/viewer.h>
11 
12 #include <iostream>
13 #include <sstream>
14 #include <chrono>
15 #include <thread>
16 
17 #include <libcurv/context.h>
18 #include <libcurv/die.h>
19 #include <libcurv/exception.h>
20 #include <libcurv/string.h>
21 
22 #define GLM_ENABLE_EXPERIMENTAL
23 #include <glm/gtc/matrix_transform.hpp>
24 #include <glm/gtx/matrix_transform_2d.hpp>
25 #include <glm/gtx/rotate_vector.hpp>
26 
27 #include "scale_picker.h"
28 #include "shapes.h"
29 
30 #include <imgui.h>
31 #include <imgui_impl_glfw.h>
32 #include <imgui_impl_opengl3.h>
33 
34 namespace curv { namespace viewer {
35 
Viewer()36 Viewer::Viewer()
37 {
38 }
39 
Viewer(const Viewer_Config & config)40 Viewer::Viewer(const Viewer_Config& config)
41 :
42     config_(config)
43 {
44 }
45 
46 void
set_shape_no_hud(const Shape_Program & shape,const Render_Opts & opts)47 Viewer::set_shape_no_hud(const Shape_Program& shape, const Render_Opts& opts)
48 {
49     set_shape(Viewed_Shape(shape, opts));
50     hud_ = false;
51 }
52 
53 void
set_shape(Viewed_Shape shape)54 Viewer::set_shape(Viewed_Shape shape)
55 {
56     // preserve picker state
57     if (!shape_.param_.empty()) {
58         for (auto pnew = shape.param_.begin();
59              pnew != shape.param_.end();
60              ++pnew)
61         {
62             auto pold = shape_.param_.find(pnew->first);
63             if (pold != shape_.param_.end()
64                 && pnew->second.pconfig_.type_ == pold->second.pconfig_.type_)
65             {
66                 pnew.value().pstate_ = pold->second.pstate_;
67             }
68         }
69     }
70 
71     shape_ = move(shape);
72     hud_ = !shape_.param_.empty();
73   #if 0
74     // describe sliders on stderr (TODO: remove debug code)
75     for (auto& i : shape_.param_) {
76         std::cerr << i.first << " :: ";
77         i.second.pconfig_.write(std::cerr);
78         std::cerr << " = ";
79         i.second.pstate_.write(std::cerr, i.second.pconfig_.type_);
80         std::cerr << "\n";
81     }
82   #endif
83     if (is_open()) {
84         error_ = false;
85         num_errors_ = 0;
86         shader_.detach(GL_FRAGMENT_SHADER | GL_VERTEX_SHADER);
87         if (!shader_.load(shape_.frag_, vertSource_, config_.verbose_))
88             error_ = true;
89         fps_.reset();
90     }
91 }
92 
93 void
run()94 Viewer::run()
95 {
96     open();
97     while (draw_frame());
98     close();
99 }
100 
open()101 void Viewer::open()
102 {
103     if (!is_open()) {
104         // Set initial default values for centre, eye and up
105         reset_view(home);
106 
107         // Initialize openGL context
108         initGL();
109 
110         // Start working on the GL context
111         setup();
112     }
113 }
114 
reset_view(viewtype view)115 void Viewer::reset_view(viewtype view )
116 {
117     // Reset the 2D camera position.
118     u_view2d_ = glm::mat3(1.);
119 
120     // Reset the 3D camera position.
121     //
122     // The 3D camera position assumes that the object being viewed
123     // is a centred, axis aligned cube of size 2,
124     // extending between -1 and 1 along the X, Y, and Z axes.
125     // The rendering code transforms coordinates based on the actual
126     // shape's bounding box to get the actual camera position.
127     //
128     // Note: the up3d vector must be orthogonal to (eye3d - centre3d),
129     // or rotation doesn't work correctly.
130 
131     // The centre is the origin.
132     u_centre3d_ = glm::vec3(0., 0., 0.);
133     if (view==upside)
134     {
135         u_eye3d_ = glm::vec3(0, 6, 0);
136         u_up3d_ = glm::vec3(0, 0, -1);
137     }
138     else if (view==downside)
139     {
140         u_eye3d_ = glm::vec3(0, -6, 0);
141         u_up3d_ = glm::vec3(0, 0, -1);
142     }
143     else if (view==leftside)
144     {
145         u_eye3d_ = glm::vec3(-6, 0, 0);
146         u_up3d_ = glm::vec3(0, 1, 0);
147     }
148     else if (view==rightside)
149     {
150         u_eye3d_ = glm::vec3(6, 0, 0);
151         u_up3d_ = glm::vec3(0, 1, 0);
152     }
153     else if (view==frontside)
154     {
155         u_eye3d_ = glm::vec3(0, 0, 6);
156         u_up3d_ = glm::vec3(0, 1, 0);
157     }
158     else if (view==backside)
159     {
160         u_eye3d_ = glm::vec3(0, 0, -6);
161         u_up3d_ = glm::vec3(0, 1, 0);
162     }
163     else
164     {
165         // 'eye3d' is derived by starting with [0,0,6], then rotating 30 degrees
166         // around the X and Y axes.
167         u_eye3d_ = glm::vec3(2.598076, 3.0, 4.5);
168 
169         // up3d is derived by starting with [0,1,0], then applying
170         // the same rotations as above, so that up3d is orthogonal to eye3d.
171         u_up3d_ = glm::vec3(-0.25, 0.866025, -0.433013);
172     }
173 }
174 
175 // Helper to display a little (?) mark which shows a tooltip when hovered.
ShowHelpMarker(const char * desc)176 static void ShowHelpMarker(const char* desc)
177 {
178     ImGui::TextDisabled("(?)");
179     if (ImGui::IsItemHovered())
180     {
181         ImGui::BeginTooltip();
182         ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
183         ImGui::TextUnformatted(desc);
184         ImGui::PopTextWrapPos();
185         ImGui::EndTooltip();
186     }
187 }
188 
draw_frame()189 bool Viewer::draw_frame()
190 {
191     if (glfwWindowShouldClose(window_))
192         return false;
193     poll_events();
194 
195     ImGui_ImplOpenGL3_NewFrame();
196     ImGui_ImplGlfw_NewFrame();
197     ImGui::NewFrame();
198     if (hud_) {
199         //ImGui::ShowDemoWindow(&hud_);
200         ImGui::PushStyleColor(ImGuiCol_WindowBg, // modify the Light style
201             (ImVec4)ImColor(230,230,230,255));
202         ImGui::SetNextWindowPos(ImVec2(0,0), ImGuiCond_Once);
203         ImGui::SetNextWindowSize(ImVec2(350,0), ImGuiCond_Once);
204         ImGui::Begin("Parameters", &hud_, 0);
205         for (auto i = shape_.param_.begin();
206              i != shape_.param_.end();
207              ++i)
208         {
209             switch (i->second.pconfig_.type_) {
210             case Picker::Type::slider:
211                 ImGui::SliderFloat(i->first.c_str(), &i.value().pstate_.num_,
212                     i->second.pconfig_.slider_.low_,
213                     i->second.pconfig_.slider_.high_);
214                 ImGui::SameLine(); ShowHelpMarker(
215                     "Click or drag slider to set value.\n"
216                     "CTRL+click to edit value as text.\n");
217                 break;
218             case Picker::Type::int_slider:
219                 ImGui::SliderInt(i->first.c_str(), &i.value().pstate_.int_,
220                     i->second.pconfig_.int_slider_.low_,
221                     i->second.pconfig_.int_slider_.high_);
222                 ImGui::SameLine(); ShowHelpMarker(
223                     "Click or drag slider to set value.\n"
224                     "CTRL+click to edit value as text.\n");
225                 break;
226             case Picker::Type::scale_picker:
227                 run_scale_picker(i->first.c_str(), &i.value().pstate_.num_);
228                 ImGui::SameLine(); ShowHelpMarker(
229                     "Drag and hold to adjust value.\n"
230                     "SHIFT+drag changes value more quickly.\n"
231                     "ALT+drag changes value more slowly.\n"
232                     "CTRL+click to edit value as text.\n");
233                 break;
234             case Picker::Type::checkbox:
235                 ImGui::Checkbox(i->first.c_str(), &i.value().pstate_.bool_);
236                 break;
237             case Picker::Type::colour_picker:
238                 ImGui::ColorEdit3(i->first.c_str(), i.value().pstate_.vec3_,
239                     ImGuiColorEditFlags_PickerHueWheel);
240                 ImGui::SameLine(); ShowHelpMarker(
241                     "Click on the coloured square to open a colour picker.\n"
242                     "To adjust a colour component, drag on numeric field,\n"
243                     "or CTRL+click to edit value as text.\n"
244                     "Right-click on the coloured square to set colour space.\n"
245                     "Drag coloured square and drop on another coloured square.\n");
246                 break;
247             }
248         }
249         if (!shape_.param_.empty()) {
250             if (ImGui::Button("Reset")) {
251                 for (auto i = shape_.param_.begin();
252                      i != shape_.param_.end();
253                      ++i)
254                 {
255                     i.value().pstate_ = i->second.default_state_;
256                 }
257             }
258             ImGui::SameLine();
259         }
260         //ImGui::Checkbox("Power Saver", &config_.lazy_); // TODO: fix
261         ImGui::End();
262         ImGui::PopStyleColor(1);
263     }
264 
265     render();
266     swap_buffers();
267     if (!config_.lazy_)
268         measure_time();
269     return true;
270 }
271 
close()272 void Viewer::close()
273 {
274     if (is_open()) {
275         //glfwGetWindowPos(window_, &window_pos_.x, &window_pos_.y);
276         glfwGetWindowSize(window_, &window_size_.x, &window_size_.y);
277         onExit();
278     }
279 }
280 
~Viewer()281 Viewer::~Viewer()
282 {
283     close();
284 }
285 
setup()286 void Viewer::setup()
287 {
288     // Prepare viewport
289     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
290     glClear(GL_COLOR_BUFFER_BIT);
291 
292     glEnable(GL_DEPTH_TEST);
293     glFrontFace(GL_CCW);
294 
295     //  Load Geometry
296     //
297     vbo_ = rect(0.0,0.0,1.0,1.0).getVbo();
298 
299     //  Build shader;
300     //
301     vertSource_ = vbo_->getVertexLayout()->getDefaultVertShader();
302     if (!shader_.load(shape_.frag_, vertSource_, config_.verbose_))
303         error_ = true;
304 
305     // Turn on Alpha blending
306     glEnable(GL_BLEND);
307     glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
308 
309     // Clear the background
310     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
311 
312     fps_.reset();
313 }
314 
render()315 void Viewer::render()
316 {
317     unsigned cnt = num_errors_;
318 
319     if (!error_) ImGui::Render();
320 
321     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
322 
323     if (!error_) {
324         shader_.use();
325 
326         // Pass uniforms
327         shader_.setUniform("u_resolution", getWindowWidth(), getWindowHeight());
328         if (shader_.needTime()) {
329             shader_.setUniform("u_time", float(current_time_));
330         }
331         if (shader_.needView2d()) {
332             shader_.setUniform("u_view2d", u_view2d_);
333         }
334         if (shader_.needView3d()) {
335             shader_.setUniform("u_eye3d", u_eye3d_);
336             shader_.setUniform("u_centre3d", u_centre3d_);
337             shader_.setUniform("u_up3d", u_up3d_);
338         }
339         glm::mat4 mvp = glm::mat4(1.);
340         shader_.setUniform("u_modelViewProjectionMatrix", mvp);
341 
342         for (auto& p : shape_.param_) {
343             // TODO: precompute uniform id
344             auto& name = p.second.identifier_;
345             switch (p.second.pconfig_.type_) {
346             case Picker::Type::slider:
347             case Picker::Type::scale_picker:
348                 shader_.setUniform(name.c_str(), float(p.second.pstate_.num_));
349                 break;
350             case Picker::Type::int_slider:
351                 shader_.setUniform(name.c_str(), float(p.second.pstate_.int_));
352                 break;
353             case Picker::Type::checkbox:
354                 shader_.setUniform(name.c_str(), int(p.second.pstate_.bool_));
355                 break;
356             case Picker::Type::colour_picker:
357                 shader_.setUniform(name.c_str(), p.second.pstate_.vec3_, 3);
358                 break;
359             default:
360                 die("picker with bad sctype");
361             }
362         }
363 
364         vbo_->draw(&shader_);
365 
366         ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
367     }
368 
369     if (num_errors_ > cnt) error_ = true;
370 }
371 
onKeyPress(int key,int mods)372 void Viewer::onKeyPress(int key, int mods)
373 {
374     bool ctrl = (mods & (GLFW_MOD_CONTROL|GLFW_MOD_SUPER)) != 0;
375     if (key == GLFW_KEY_Q || (key == GLFW_KEY_W && ctrl)) {
376         glfwSetWindowShouldClose(window_, GL_TRUE);
377     }
378     else if (key == GLFW_KEY_HOME) { // reset to default
379         reset_view(home);
380     }
381     else if (key == GLFW_KEY_U) {
382         reset_view(upside);
383     }
384     else if (key == GLFW_KEY_D) {
385         reset_view(downside);
386     }
387     else if (key == GLFW_KEY_L) {
388         reset_view(leftside);
389     }
390     else if (key == GLFW_KEY_R) {
391         reset_view(rightside);
392     }
393     else if (key == GLFW_KEY_F) {
394         reset_view(frontside);
395     }
396     else if (key == GLFW_KEY_B) {
397         reset_view(backside);
398     }
399 }
400 
onScroll(float _yoffset)401 void Viewer::onScroll(float _yoffset)
402 {
403     // Vertical scroll button zooms u_view2d_ and view3d.
404     /* zoomfactor 2^(1/4): 4 scroll wheel clicks to double in size. */
405     constexpr float zoomfactor = 1.1892;
406     if (_yoffset != 0) {
407         float z = pow(zoomfactor, _yoffset);
408 
409         // zoom view2d
410         glm::vec2 zoom = glm::vec2(z,z);
411         glm::vec2 origin = {getWindowWidth()/2, getWindowHeight()/2};
412         u_view2d_ = glm::translate(u_view2d_, origin);
413         u_view2d_ = glm::scale(u_view2d_, zoom);
414         u_view2d_ = glm::translate(u_view2d_, -origin);
415 
416         // zoom view3d
417         u_eye3d_ = u_centre3d_ + (u_eye3d_ - u_centre3d_)*z;
418     }
419 }
420 
onMouseDrag(float _x,float _y,int _button)421 void Viewer::onMouseDrag(float _x, float _y, int _button)
422 {
423     if (_button == 1){
424         // Left-button drag is used to pan u_view2d_.
425         u_view2d_ = glm::translate(u_view2d_, -mouse_.velocity);
426 
427         // Left-button drag is used to rotate eye3d around centre3d.
428         // One complete drag across the screen width equals 360 degrees.
429         constexpr double tau = 6.283185307179586;
430         u_eye3d_ -= u_centre3d_;
431         u_up3d_ -= u_centre3d_;
432         // Rotate about vertical axis, defined by the 'up' vector.
433         float xangle = (mouse_.velocity.x / getWindowWidth()) * tau;
434         u_eye3d_ = glm::rotate(u_eye3d_, -xangle, u_up3d_);
435         // Rotate about horizontal axis, which is perpendicular to
436         // the (centre3d,eye3d,up3d) plane.
437         float yangle = (mouse_.velocity.y / getWindowHeight()) * tau;
438         glm::vec3 haxis = glm::cross(u_eye3d_-u_centre3d_, u_up3d_);
439         u_eye3d_ = glm::rotate(u_eye3d_, -yangle, haxis);
440         u_up3d_ = glm::rotate(u_up3d_, -yangle, haxis);
441         //
442         u_eye3d_ += u_centre3d_;
443         u_up3d_ += u_centre3d_;
444     } else {
445         // TODO: rotate view2d.
446 
447         // pan view3d.
448         float dist3d = glm::length(u_eye3d_ - u_centre3d_);
449         glm::vec3 voff = glm::normalize(u_up3d_)
450             * (mouse_.velocity.y/getWindowHeight()) * dist3d;
451         u_centre3d_ -= voff;
452         u_eye3d_ -= voff;
453         glm::vec3 haxis = glm::cross(u_eye3d_-u_centre3d_, u_up3d_);
454         glm::vec3 hoff = glm::normalize(haxis)
455             * (mouse_.velocity.x/getWindowWidth()) * dist3d;
456         u_centre3d_ += hoff;
457         u_eye3d_ += hoff;
458     }
459 }
460 
onExit()461 void Viewer::onExit()
462 {
463     // clear screen
464     glClear( GL_COLOR_BUFFER_BIT );
465 
466     // close openGL instance
467     closeGL();
468 
469     // DELETE RESOURCES
470     delete vbo_;
471 }
472 
473 const char appTitle[] = "curv";
474 
475 extern "C" void
MessageCallback(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar * message,const void * userParam)476 MessageCallback(
477     GLenum source,
478     GLenum type,
479     GLuint id,
480     GLenum severity,
481     GLsizei length,
482     const GLchar* message,
483     const void* userParam)
484 {
485     (void) length;
486     Viewer* viewer = (Viewer*)userParam;
487     if (viewer->config_.verbose_ || severity == GL_DEBUG_SEVERITY_HIGH) {
488         std::cerr << "GL ";
489         switch (type) {
490         case GL_DEBUG_TYPE_ERROR:
491             std::cerr << "ERROR"; break;
492         case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
493             std::cerr << "DEPRECATED"; break;
494         case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
495             std::cerr << "UNDEFINED"; break;
496         case GL_DEBUG_TYPE_PORTABILITY:
497             std::cerr << "PORTABILITY"; break;
498         case GL_DEBUG_TYPE_PERFORMANCE:
499             std::cerr << "PERFORMANCE"; break;
500         case GL_DEBUG_TYPE_MARKER:
501             std::cerr << "MARKER"; break;
502         case GL_DEBUG_TYPE_PUSH_GROUP:
503             std::cerr << "PUSH_GROUP"; break;
504         case GL_DEBUG_TYPE_POP_GROUP:
505             std::cerr << "POP_GROUP"; break;
506         case GL_DEBUG_TYPE_OTHER:
507             std::cerr << "OTHER"; break;
508         default:
509             std::cerr << "<"<<type<<">";
510         }
511         std::cerr << ": src=" << source
512             << ",id=" << id
513             << ",sev=" << severity
514             << " " << message << "\n";
515     }
516     if (severity == GL_DEBUG_SEVERITY_HIGH) ++viewer->num_errors_;
517 }
518 
initGL()519 void Viewer::initGL()
520 {
521     glfwSetErrorCallback([](int err, const char* msg)->void {
522         std::cerr << "GLFW error 0x"<<std::hex<<err<<std::dec<<": "<<msg<<"\n";
523     });
524     if(!glfwInit()) {
525         std::cerr << "ABORT: GLFW init failed" << std::endl;
526         exit(-1);
527     }
528 
529     // Set window and context parameters.
530     glfw_set_context_parameters();
531     if (headless_) {
532         glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
533     }
534 
535     // Create window, create OpenGL context, load OpenGL library.
536     window_ = glfwCreateWindow(window_size_.x, window_size_.y, appTitle,
537         NULL, NULL);
538     if(!window_) {
539         glfwTerminate();
540         std::cerr << "ABORT: GLFW create window failed" << std::endl;
541         exit(-1);
542     }
543     glfwMakeContextCurrent(window_);
544     if (!opengl_init()) {
545         std::cerr << "ABORT: Can't load OpenGL library\n";
546         exit(-1);
547     }
548 
549     // Enable OpenGL debugging, so I can print messages when errors occur.
550     error_ = false;
551     num_errors_ = 0;
552     if (GLAD_GL_KHR_debug) {
553         glEnable(GL_DEBUG_OUTPUT);
554         glDebugMessageCallback(MessageCallback, (void*)this);
555     }
556 
557     // The GL context is now set up and ready for use.
558 
559     glfwSwapInterval(1);
560 
561     // Create and bind a VAO (Vertex Array Object) for later use.
562     glGenVertexArrays(1, &vao_);
563     glBindVertexArray(vao_);
564 
565     glfwSetWindowUserPointer(window_, (void*)this);
566 
567     setWindowSize(window_size_.x, window_size_.y);
568     if (!have_window_pos_) {
569         glfwGetWindowPos(window_, &window_pos_.x, &window_pos_.y);
570         have_window_pos_ = true;
571     } else {
572         glfwSetWindowPos(window_, window_pos_.x, window_pos_.y);
573     }
574 
575     // Initialize ImGUI
576     IMGUI_CHECKVERSION();
577     ImGui::CreateContext();
578     ImGuiIO& io = ImGui::GetIO(); (void)io;
579     io.IniFilename = nullptr;
580     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls
581     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;   // Enable Gamepad Controls
582     ImGui_ImplGlfw_InitForOpenGL(window_, false);
583     ImGui_ImplOpenGL3_Init(glsl_version);
584     //ImGui::StyleColorsDark();
585     //ImGui::StyleColorsClassic();
586     ImGui::StyleColorsLight();
587 
588     // Install event callbacks.
589     glfwSetWindowSizeCallback(window_,
590         [](GLFWwindow* win, int w, int h) {
591             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
592             self->setWindowSize(w,h);
593         });
594 
595     glfwSetKeyCallback(window_,
596         [](GLFWwindow* win, int key, int scancode, int action, int mods) {
597             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
598             ImGuiIO& io = ImGui::GetIO();
599             if (key == GLFW_KEY_H && (mods & GLFW_MOD_CONTROL) && action == GLFW_PRESS)
600                 self->hud_ = !self->hud_;
601             else {
602                 ImGui_ImplGlfw_KeyCallback(win, key, scancode, action, mods);
603                 if (!io.WantCaptureKeyboard && action == GLFW_PRESS)
604                     self->onKeyPress(key, mods);
605             }
606         });
607 
608     glfwSetCharCallback(window_, ImGui_ImplGlfw_CharCallback);
609 
610     // callback when a mouse button is pressed or released
611     glfwSetMouseButtonCallback(window_,
612         [](GLFWwindow* win, int button, int action, int mods) {
613             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
614             ImGuiIO& io = ImGui::GetIO();
615             ImGui_ImplGlfw_MouseButtonCallback(win, button, action, mods);
616             if (!io.WantCaptureMouse && action == GLFW_PRESS) {
617                 self->mouse_.drag.x = self->mouse_.x;
618                 self->mouse_.drag.y = self->mouse_.y;
619             }
620         });
621 
622     glfwSetScrollCallback(window_,
623         [](GLFWwindow* win, double xoffset, double yoffset) {
624             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
625             ImGuiIO& io = ImGui::GetIO();
626             ImGui_ImplGlfw_ScrollCallback(win, xoffset, yoffset);
627             if (!io.WantCaptureMouse)
628                 self->onScroll(-yoffset * self->fPixelDensity_);
629         });
630 
631     // callback when the mouse cursor moves
632     glfwSetCursorPosCallback(window_,
633         [](GLFWwindow* win, double x, double y) {
634             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
635             ImGuiIO& io = ImGui::GetIO();
636             if (!io.WantCaptureMouse)
637                 self->onMouseMove(x, y);
638         });
639 
640     glfwSetWindowPosCallback(window_,
641         [](GLFWwindow* win, int x, int y) {
642             Viewer* self = (Viewer*) glfwGetWindowUserPointer(win);
643             self->window_pos_.x = x;
644             self->window_pos_.y = y;
645             if (self->fPixelDensity_ != self->getPixelDensity()) {
646                 self->setWindowSize(self->viewport_.x, self->viewport_.y);
647             }
648         });
649 }
650 
onMouseMove(double x,double y)651 void Viewer::onMouseMove(double x, double y)
652 {
653     // Convert x,y to pixel coordinates relative to viewport.
654     // (0,0) is lower left corner.
655     y = viewport_.y - y;
656     x *= fPixelDensity_;
657     y *= fPixelDensity_;
658     // mouse_.velocity is the distance the mouse cursor has moved
659     // since the last callback, during a drag gesture.
660     // mouse_.drag is the previous mouse position, during a drag gesture.
661     // Note that mouse_.drag is *not* constrained to the viewport.
662     mouse_.velocity.x = x - mouse_.drag.x;
663     mouse_.velocity.y = y - mouse_.drag.y;
664     mouse_.drag.x = x;
665     mouse_.drag.y = y;
666 
667     // mouse_.x,mouse_.y is the current cursor position, constrained
668     // to the viewport.
669     mouse_.x = x;
670     mouse_.y = y;
671     if (mouse_.x < 0) mouse_.x = 0;
672     if (mouse_.y < 0) mouse_.y = 0;
673     if (mouse_.x > viewport_.x * fPixelDensity_) mouse_.x = viewport_.x * fPixelDensity_;
674     if (mouse_.y > viewport_.y * fPixelDensity_) mouse_.y = viewport_.y * fPixelDensity_;
675 
676     /*
677      * TODO: the following code would best be moved into the
678      * mouse button callback. If you click the mouse button without
679      * moving the mouse, then using this code, the mouse click doesn't
680      * register until the cursor is moved. (@doug-moen)
681      */
682     int action1 = glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_1);
683     int action2 = glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_2);
684     int button = 0;
685 
686     if (action1 == GLFW_PRESS) button = 1;
687     else if (action2 == GLFW_PRESS) button = 2;
688 
689     if (mouse_.button == 0 && button != mouse_.button) {
690         mouse_.button = button;
691     }
692     else {
693         mouse_.button = button;
694     }
695 
696     if (mouse_.velocity.x != 0.0 || mouse_.velocity.y != 0.0) {
697         if (button != 0) onMouseDrag(mouse_.x,mouse_.y,mouse_.button);
698     }
699 }
700 
measure_time()701 void Viewer::measure_time()
702 {
703     double now = glfwGetTime();
704     double delta = now - current_time_;
705     current_time_ = now;
706 
707     fps_.delta_time_ += delta;
708     ++fps_.frames_;
709     // Don't update the FPS more than once per second.
710     if (now - fps_.last_reported_ >= 1.0) {
711         fps_.last_reported_ = now;
712         double frame_time = fps_.delta_time_ / fps_.frames_;
713         fps_.frames_ = 0;
714         fps_.delta_time_ = 0.0;
715 
716         // display FPS in window title
717         char title[sizeof appTitle + 60];
718         snprintf(title, sizeof title, "%s ms/frame: %.2f FPS: %.2f",
719             appTitle,
720             frame_time*1000.0,
721             1.0/frame_time);
722         glfwSetWindowTitle(window_, title);
723     }
724 }
725 
poll_events()726 void Viewer::poll_events()
727 {
728     if (config_.lazy_)
729         glfwWaitEvents();
730     else
731         glfwPollEvents();
732 }
733 
swap_buffers()734 void Viewer::swap_buffers()
735 {
736     glfwSwapBuffers(window_);
737 }
738 
closeGL()739 void Viewer::closeGL()
740 {
741     //glfwSetWindowShouldClose(window_, GL_TRUE);
742     ImGui_ImplOpenGL3_Shutdown();
743     ImGui_ImplGlfw_Shutdown();
744     ImGui::DestroyContext();
745     glfwDestroyWindow(window_);
746     glfwPollEvents(); // work around GLFW bug #1412
747     window_ = nullptr;
748 }
749 
setWindowSize(int _width,int _height)750 void Viewer::setWindowSize(int _width, int _height)
751 {
752     viewport_.x = _width;
753     viewport_.y = _height;
754     fPixelDensity_ = getPixelDensity();
755     glViewport(0.0, 0.0, (float)getWindowWidth(), (float)getWindowHeight());
756 }
757 
getPixelDensity()758 float Viewer::getPixelDensity()
759 {
760     int window_width, window_height, framebuffer_width, framebuffer_height;
761     glfwGetWindowSize(window_, &window_width, &window_height);
762     glfwGetFramebufferSize(window_, &framebuffer_width, &framebuffer_height);
763     return float(framebuffer_width)/float(window_width);
764 }
765 
getWindowWidth()766 int Viewer::getWindowWidth()
767 {
768     return viewport_.x*fPixelDensity_;
769 }
770 
getWindowHeight()771 int Viewer::getWindowHeight()
772 {
773     return viewport_.y*fPixelDensity_;
774 }
775 
776 }} // namespace
777