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