Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:49:37

0001 #pragma once
0002 /**
0003 SGLFW.h : Light touch OpenGL render loop and key handling
0004 ===========================================================
0005 
0006 Light touch encapsulation of OpenGL window and shader program,
0007 that means trying to hide boilerplate, but not making lots of
0008 decisions for user and getting complicated and inflexible like
0009 the old oglrap/Frame.hh oglrap/OpticksViz did.
0010 
0011 WASD : Navigation in 3D space
0012 -------------------------------
0013 
0014 * FPS : first-person shooters
0015 * https://learnopengl.com/Getting-started/Camera
0016 
0017 
0018 * :google:`mouse interface to move around 3D environment`
0019 * https://www.researchgate.net/publication/220863069_Comparison_of_3D_Navigation_Interfaces
0020 
0021 For "first-person shooters", navigation typically uses a combination of mouse
0022 and keyboard. The keyboard keys "WASD" are used for movement forward, a side
0023 step (“strafe”) left, backward, and a side step right, respectively. The users
0024 view direction is controlled by the mouse so that moving the mouse rotates the
0025 view direction of the users head.
0026 
0027 To change the view direction the user presses and
0028 holds either of the side mouse buttons. When pressing and
0029 holding either side button the cursor disappears as a secondary
0030 indication that the view direction is tethered to the mouse
0031 movement. Likewise, when the buttons are not pressed the
0032 cursor is displayed so the user can then use it to select (and
0033 manipulate) objects. This user interface implementation
0034 enables a seamless transition between navigation and
0035 manipulation without the need for designating a button or keys
0036 solely for mode switching.
0037 
0038 An important attribute to note about this interface is that
0039 some movements can only be achieved by using the keyboard
0040 and mouse buttons in conjunction. To turn while moving
0041 forward or backward it is necessary to hold down the ‘W’ or
0042 ‘S’ key simultaneously with a mouse side button, and then
0043 moving the mouse left or right to “steer”. Orbiting an object
0044 can also be achieved by holding ‘A’ or ‘D’ simultaneously
0045 with a side mouse button and moving the mouse to change the
0046 view direction simultaneously while the user side steps. This
0047 makes this user interface more powerful than ‘Click-to-Move’.
0048 If done properly, one can keep a point of the virtual
0049 environment fixed in the center of the view while moving
0050 around it.
0051 
0052 envvars
0053 ---------
0054 
0055 
0056 **/
0057 
0058 #include <cassert>
0059 #include <cstring>
0060 #include <iostream>
0061 #include <iomanip>
0062 #include <string>
0063 #include <sstream>
0064 #include <vector>
0065 
0066 #include <GL/glew.h>
0067 #include <GLFW/glfw3.h>
0068 #ifndef GLFW_TRUE
0069 #define GLFW_TRUE true
0070 #endif
0071 
0072 #include "GL_CHECK.h"
0073 
0074 #define GLEQ_IMPLEMENTATION
0075 #include "gleq.h"
0076 
0077 #include <glm/glm.hpp>
0078 #include "NPU.hh"
0079 
0080 #include "ssys.h"
0081 #include "spath.h"
0082 #include "schrono.h"
0083 
0084 #include "SMesh.h"
0085 #include "SGLM.h"
0086 
0087 #include "SGLFW_check.h"
0088 #include "SGLFW_Buffer.h"
0089 #include "SGLFW_VAO.h"
0090 #include "SGLFW_Attrib.h"
0091 #include "SGLFW_Program.h"
0092 #include "SGLFW_Mesh.h"
0093 #include "SGLFW_GLEQ.h"
0094 
0095 #ifdef WITH_CUDA_GL_INTEROP
0096 #include "SGLFW_CUDA.h"
0097 #endif
0098 
0099 
0100 #include "SGLFW_Keys.h"
0101 
0102 #include "SIMG_Frame.h"
0103 
0104 
0105 struct SGLFW : public SCMD
0106 {
0107     static constexpr const char* HELP =  R"LITERAL(
0108 SGLFW.h
0109 ========
0110 
0111 ::
0112 
0113 
0114                  Q  W
0115                  | /
0116                  |/
0117              A---+---D
0118                 /|
0119                / |
0120               S  E
0121 
0122 
0123 
0124 SPACE
0125    toggle gm.toggle.stop : when stopped mouse movement does nothing
0126 
0127 A
0128    (WASDQE) hold to change eyeshift, translate left
0129 
0130 alt+A
0131    toggle gm.option.A controlling rendering of SRecord A
0132    (use T0 T1 and TN to control time range and animation speed)
0133 
0134 B
0135    -
0136 
0137 alt+B
0138    toggle gm.option.B controlling rendering of SRecord B
0139    (use T0 T1 and TN to control time range and animation speed)
0140 
0141 C
0142    gm.toggle.cuda between rasterized and raytrace render
0143    [currently only implemented for sysrap triangulated renderer]
0144 D
0145    (WASDQE) hold to change eyeshift, translate right
0146 E
0147    (WASDQE) hold to change eyeshift, translate down
0148 F
0149    gm.toggle.tmax : then change far by moving cursor vertically
0150 G
0151    -
0152 alt+G
0153    toggle gm.option.G controlling rendering of SGen
0154 
0155 H
0156    invokes SGLM::home returning to initial position with no lookrotation or eyeshift [--home]
0157 
0158 alt+H
0159    dumps this help string [--help]
0160 
0161 I
0162    --snap-local : taking a screenshot
0163 J
0164    --snap-local-inverted : taking a y-inverted screenshot
0165 K
0166    save screen shot [--snap]
0167 L
0168    toggle between world spin modes
0169 L(formerly)
0170    save Y inverted screen shot [--snap-inverted]
0171 
0172 M
0173    hop to the MOI envvar configured frame [not supported by all renderers]
0174 
0175 alt+M
0176    toggle gm.option.M controlling rendering of mesh geometry
0177 
0178 N
0179    gm.toggle.tmin : then change near by moving cursor vertically
0180 O
0181    switch camera between perspective and orthographic projections [--tcam]
0182 
0183 alt+O
0184    option.O toggle display of optix ray traced geometry
0185 
0186 P
0187    invokes SGLM::desc describing view parameters [--desc]
0188 Q
0189    (WASDQE) hold to change eyeshift, translate up
0190 
0191 R
0192    hold down while arcball dragging the mouse to change the look rotation.
0193    This means that can make large shifts in viewpoint in the form of rotating the
0194    viewpoint around the look point, which is normally the origin of the target frame.
0195    Conversely that can be considered to look like rotation of the target object.
0196 
0197 S
0198    (WASDQE) hold to change eyeshift, translate backwards
0199 
0200 T:gm.toggle.time
0201    toggles simulation time scrubbing for record arrays,
0202    when enabled moving mouse up/down changes the simulation time.
0203    Adjust mouse movement sensitivity with TIMESCALE envvar, default 1.0
0204    During scrubbing the automated time increments are disabled.
0205 
0206 alt+T
0207    gm.reset_time back to t0(T0) for the alt-A and alt-B enabled record array renders
0208 
0209 shift+T
0210    gm.reset_time_TT to TT for  alt-A and alt-B enabled record array renders
0211 
0212 ctrl+T
0213    toggles gm.enabled_time_halt
0214 
0215 U:gm.toggle.norm
0216    for rasterized render toggle between wireframe and normal shading
0217 V
0218    [--traceyflip] invert vertical of the raytrace render, via Param.h signal to kernel
0219 W
0220    (WASDQE) hold to change eyeshift, translate forwards
0221 X
0222    [--rendertype] toggle between normal and zdepth shading
0223    [only implemented for analytic ray trace, not triangulated ray trace]
0224 
0225 Y
0226    controls eyerotation, hold down and drag mouse around to change eyerotation,
0227    allowing to "turn around"
0228 
0229 
0230 Z:gm.toggle.zoom
0231    change zoom scaling by moving cursor vertically
0232 
0233 
0234 0,1,2,3,4,5,6,7,8,9
0235    hop to frame "num" or default if no such frame
0236 
0237 0,1,2,3,4,5,6,7,8,9 + SHIFT
0238    hop to frame "num + 10" using offset for SHIFT modifier
0239 
0240 0,1,2,3,4,5,6,7,8,9 + ALT
0241    hop to frame "num + 20" using offset for ALT modifier
0242    (hop to default frame if there is no frame with the index)
0243 
0244 0,1,2,3,4,5,6,7,8,9 + SHIFT + ALT
0245    hop to frame "num + 30" using offset for SHIFT and ALT modifier
0246    (hop to default frame if there is no frame with the index)
0247 
0248 With all num_key frame selection is there is no frame with the index
0249 then hop to the default frame.
0250 
0251 
0252 TODO: panning toggle, to enable translating viewpoint "sideways" (ie keeping the
0253 direction of the view the same, but translating in perpendicular direction
0254 selected by mouse drag direction, old Opticks oglrap may have this)
0255 
0256 TODO: automated view rotation around an axis, eg +Z
0257 
0258 
0259 )LITERAL" ;
0260     static constexpr const char* TITLE = "SGLFW" ;
0261     static constexpr const char* _SLEEP_BREAK = "SGLFW__SLEEP_BREAK" ;
0262 
0263     SGLM& gm ;
0264 
0265 
0266     int level ;
0267     int sleep_break ;
0268     int wanted_frame_idx ;
0269     int wanted_snap ;
0270 
0271     int width ;
0272     int height ;
0273     int depth_test ;
0274 
0275     const char* title ;
0276     GLFWwindow* window ;
0277 
0278     int count ;
0279     int renderlooplimit ;
0280     int renderloop_tail_LEVEL ;
0281     bool exitloop ;
0282 
0283     bool dump ;
0284     int  _width ;  // on retina 2x width
0285     int  _height ;
0286     SIMG_Frame*  sif ;
0287     SIMG_Frame*  sid ;
0288 
0289     // getStartPos
0290     double _start_x ;
0291     double _start_y ;
0292     int cursor_moved_count ;
0293 
0294     glm::vec2 start_ndc ;  // from key_pressed
0295     glm::vec2 move_ndc ;   // from cursor_moved
0296     glm::vec4 drag ;
0297 
0298 
0299     //SGLFW_Toggle toggle = {} ; // moved to SGLM
0300     SGLFW_Keys keys = {} ;
0301 
0302 
0303 
0304     bool renderloop_proceed();
0305     void renderloop_exit();
0306     void renderloop_head();
0307 
0308     static constexpr const char* SGLFW__renderloop_tail_LEVEL = "SGLFW__renderloop_tail_LEVEL" ;
0309     void renderloop_tail();
0310 
0311     void handle_event(GLEQevent& event);
0312     void post_handle_key();
0313 
0314 
0315     void framebuffer_resized();
0316     void window_refresh();
0317     void key_pressed(unsigned key);
0318     void numkey_pressed(unsigned num, unsigned modifiers);
0319 
0320     void set_wanted_frame_idx(int _idx);
0321     int  get_wanted_frame_idx() const ;
0322     void handle_frame_hop();
0323 
0324 
0325     void snap(int w);
0326     void set_wanted_snap(int w);
0327     int get_wanted_snap() const ;
0328     void handle_snap();
0329 
0330 
0331     void download_pixels();
0332     void download_depth();
0333     void init_img_frame();
0334     void writeJPG(const char* path) const ;
0335     void snap_local(bool yflip);
0336 
0337     void key_repeated(unsigned key);
0338     void key_released(unsigned key);
0339 
0340     void button_pressed(unsigned button, unsigned mods);
0341     void button_released(unsigned button, unsigned mods);
0342 
0343 
0344     void cursor_moved(int ix, int iy);
0345     void cursor_moved_action();
0346 
0347     int command(const char* cmd);
0348     static void Help();
0349     void home();
0350     void _desc();
0351     void tcam();
0352     void traceyflip();
0353     void rendertype();
0354     static std::string FormCommand(const char* token, float value);
0355 
0356     void getWindowSize();
0357     std::string descWindowSize() const;
0358 
0359     void setCursorPos(float ndc_x, float ndc_y);
0360     void setCursorPos_home();
0361     void getStartPos();
0362     std::string descDrag() const;
0363     std::string descStartPos() const;
0364 
0365     SGLFW(SGLM& gm );
0366 
0367     virtual ~SGLFW();
0368     static void Error_callback(int error, const char* description);
0369     void init();
0370     void setWindowTitle(const char* title);
0371 
0372 };
0373 
0374 /**
0375 SGLFW::renderloop_proceed
0376 ----------------------------
0377 
0378 For envvar SGLFW__SLEEP_BREAK:1 the
0379 renderloop is exited after one second of
0380 sleep before doing any render.
0381 Some debug ?
0382 
0383 **/
0384 
0385 inline bool SGLFW::renderloop_proceed()
0386 {
0387     if(sleep_break == 1)
0388     {
0389         std::cout << "SGLFW::renderloop_proceed " << _SLEEP_BREAK << std::endl;
0390         schrono::sleep(1);
0391         exitloop = true ;
0392     }
0393     bool proceed = !glfwWindowShouldClose(window) && !exitloop ;
0394     return proceed ;
0395 }
0396 inline void SGLFW::renderloop_exit()
0397 {
0398     if(level > 0) std::cout << "SGLFW::renderloop_exit" << std::endl;
0399     glfwSetWindowShouldClose(window, true);
0400 }
0401 
0402 
0403 /**
0404 SGLFW::renderloop_head
0405 ------------------------
0406 
0407 HMM: perhaps handle_frame_hop from here ?
0408 
0409 **/
0410 
0411 
0412 inline void SGLFW::renderloop_head()
0413 {
0414     dump = level > 0 && count % 100000 == 0 ;
0415 
0416     getWindowSize();
0417     glViewport(0, 0, _width, _height);
0418     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
0419 
0420     if(dump) std::cout << "SGLFW::renderloop_head" << " gl.count " << count << std::endl ;
0421 
0422     if(count == 0 ) home();
0423 
0424     gm.renderloop_head();
0425 }
0426 
0427 
0428 inline void SGLFW::renderloop_tail()
0429 {
0430     glfwSwapBuffers(window);
0431     glfwPollEvents();
0432 
0433     GLEQevent event;
0434     while (gleqNextEvent(&event))
0435     {
0436         handle_event(event);
0437         gleqFreeEvent(&event);
0438     }
0439 
0440     exitloop = renderlooplimit > 0 && count++ > renderlooplimit ;
0441 
0442     gm.renderloop_tail();
0443 
0444     const char* gm_title = gm.title.c_str();
0445     setWindowTitle(gm_title);
0446     if(renderloop_tail_LEVEL > 0 && count < 2) std::cout
0447         << "SGLFW::renderloop_tail "
0448         << " count " << count
0449         << " gm.title [" << gm_title << "]"
0450         << " " << gm.descModelMatrix()
0451         << "\n"
0452         ;
0453 }
0454 
0455 
0456 
0457 
0458 
0459 /**
0460 SGLFW::handle_event
0461 --------------------
0462 
0463 See oglrap/Frame::handle_event
0464 
0465 **/
0466 
0467 inline void SGLFW::handle_event(GLEQevent& event)
0468 {
0469     if(level > 1) std::cout << "SGLFW::handle_event " << SGLFW_GLEQ::Name(event.type) << std::endl;
0470     switch(event.type)
0471     {
0472         case GLEQ_FRAMEBUFFER_RESIZED: framebuffer_resized()            ; break ;
0473         case GLEQ_KEY_PRESSED:   key_pressed( event.keyboard.key)       ; break ;
0474         case GLEQ_KEY_REPEATED:  key_repeated(event.keyboard.key)       ; break ;
0475         case GLEQ_KEY_RELEASED:  key_released(event.keyboard.key)       ; break ;
0476         case GLEQ_BUTTON_PRESSED:  button_pressed(  event.mouse.button, event.mouse.mods)  ; break ;
0477         case GLEQ_BUTTON_RELEASED: button_released( event.mouse.button, event.mouse.mods)  ; break ;
0478         case GLEQ_CURSOR_MOVED:    cursor_moved(event.pos.x, event.pos.y) ; break ;
0479         case GLEQ_WINDOW_REFRESH:  window_refresh()                       ; break ;
0480         default:                                                          ; break ;
0481     }
0482 
0483     if( event.type == GLEQ_KEY_PRESSED || event.type == GLEQ_KEY_REPEATED || event.type == GLEQ_KEY_RELEASED )
0484     {
0485         post_handle_key();
0486     }
0487 }
0488 
0489 inline void SGLFW::post_handle_key()
0490 {
0491     //gm.enabled_time_bump = !gm.toggle.time ;   // prevent auto time increments when are doing that with manual time scrubbing
0492     gm.enabled_time_bump = gm.toggle.time.value == 0 ;
0493 }
0494 
0495 
0496 /**
0497 SGLFW::framebuffer_resized
0498 ---------------------------
0499 
0500 Observed to be the first GLEQ event to arrive
0501 
0502 **/
0503 
0504 inline void SGLFW::framebuffer_resized()
0505 {
0506 }
0507 
0508 
0509 /**
0510 SGLFW::window_refresh
0511 ----------------------
0512 
0513 By observation this event fires only in initialization
0514 
0515 **/
0516 
0517 inline void SGLFW::window_refresh()
0518 {
0519 }
0520 
0521 
0522 /**
0523 SGLFW::key_pressed
0524 --------------------
0525 
0526 HMM:dont like the arbitrary split between here and SGLM.h for interaction control
0527 Maybe remove the toggle or do that in SGLM ?
0528 Better to do all key handling in one place to avoid confusion for duplicated usage.
0529 
0530 Q: Where is WASD navigation control ?
0531 A: SGLFW_Keys::modifiers : when WASDQERY keys are down corresponding modifier enum bits are set
0532    SGLFW_Modifiers::IsW/A/S/D/Z/X/Q/E/R/Y : interprets the modifier bit field
0533    SGLM::key_pressed_action : acts on the modified to change the eyeshift vector in 6 directions
0534 
0535 **/
0536 
0537 
0538 inline void SGLFW::key_pressed(unsigned key)
0539 {
0540     keys.key_pressed(key);
0541     unsigned modifiers = keys.modifiers() ;
0542 
0543     getStartPos();
0544     if(level > 1) std::cout
0545         << descStartPos()
0546         << descWindowSize()
0547         << std::endl
0548         ;
0549 
0550     // other than number keys could require all the below
0551     // to not have any modifiers
0552 
0553 
0554     switch(key)
0555     {
0556         case GLFW_KEY_0:
0557         case GLFW_KEY_1:
0558         case GLFW_KEY_2:
0559         case GLFW_KEY_3:
0560         case GLFW_KEY_4:
0561         case GLFW_KEY_5:
0562         case GLFW_KEY_6:
0563         case GLFW_KEY_7:
0564         case GLFW_KEY_8:
0565         case GLFW_KEY_9:
0566                               numkey_pressed(key - GLFW_KEY_0, modifiers) ; break ;
0567     }
0568 
0569     if(SGLM_Modifiers::IsNone(modifiers))
0570     {
0571         switch(key)
0572         {
0573             case GLFW_KEY_M:      set_wanted_frame_idx(-2)           ; break ;   // MOI target
0574             case GLFW_KEY_Z:      gm.toggle.zoom.next()              ; break ;   // HMM: also in SGLM_Modnav
0575             case GLFW_KEY_N:      gm.toggle.tmin.next()              ; break ;
0576             case GLFW_KEY_F:      gm.toggle.tmax.next()              ; break ;
0577             case GLFW_KEY_R:      gm.toggle.lrot.next()              ; break ;    // HMM: also in SGLM_Modnav
0578             case GLFW_KEY_C:      gm.toggle.cuda.next()              ; break ;
0579             case GLFW_KEY_U:      gm.toggle.norm.next()              ; break ;
0580             case GLFW_KEY_T:      gm.toggle.time.next()              ; break ;
0581             case GLFW_KEY_SPACE:  gm.toggle.stop.next()              ; break ;
0582             case GLFW_KEY_P:      command("--desc")                  ; break ;
0583             case GLFW_KEY_H:      command("--home")                  ; break ;
0584             case GLFW_KEY_O:      command("--tcam")                  ; break ;
0585             case GLFW_KEY_I:      command("--snap-local")            ; break ;
0586             case GLFW_KEY_J:      command("--snap-local-inverted")   ; break ;
0587             case GLFW_KEY_K:      command("--snap")                  ; break ;
0588             //case GLFW_KEY_L:      command("--snap-inverted")         ; break ;
0589             case GLFW_KEY_L:      gm.toggle.spin.next()              ; break ;
0590             case GLFW_KEY_V:      command("--traceyflip")            ; break ;
0591             case GLFW_KEY_X:      command("--rendertype")            ; break ;   // HMM: also in SGLM_Modnav
0592             case GLFW_KEY_ESCAPE: command("--exit")                  ; break ;
0593 
0594 
0595             case GLFW_KEY_W:   // WASDQE keys control navigation via SGLM_Modnav
0596             case GLFW_KEY_A:
0597             case GLFW_KEY_S:
0598             case GLFW_KEY_D:
0599             case GLFW_KEY_Q:
0600             case GLFW_KEY_E:
0601             case GLFW_KEY_Y:  // Y : mouse control of eyerotation included in SGLM_Modnav
0602                                  ; break ;
0603         }
0604     }
0605     else if( SGLM_Modifiers::IsAlt(modifiers))
0606     {
0607         switch(key)
0608         {
0609             case GLFW_KEY_A:   gm.option.A = !gm.option.A     ; break ;
0610             case GLFW_KEY_B:   gm.option.B = !gm.option.B     ; break ;
0611             case GLFW_KEY_G:   gm.option.G = !gm.option.G     ; break ;
0612             case GLFW_KEY_M:   gm.option.M = !gm.option.M     ; break ;
0613             case GLFW_KEY_O:   gm.option.O = !gm.option.O     ; break ;
0614             case GLFW_KEY_T:   gm.reset_time()                ; break ;
0615             case GLFW_KEY_H:   command("--help")              ; break ;
0616         }
0617     }
0618     else if( SGLM_Modifiers::IsShift(modifiers))
0619     {
0620         switch(key)
0621         {
0622             case GLFW_KEY_T:   gm.reset_time_TT()             ; break ;
0623         }
0624     }
0625     else if( SGLM_Modifiers::IsControl(modifiers))
0626     {
0627         switch(key)
0628         {
0629             case GLFW_KEY_T:   gm.toggle_time_halt()             ; break ;
0630         }
0631     }
0632 
0633 
0634     //if(modifiers > 0) gm.key_pressed_action(modifiers) ; // not triggered repeatedly enough for navigation
0635     if(level > 1) std::cout << gm.toggle.desc() << std::endl ;
0636     if(level > 1) std::cout << gm.option.desc() << std::endl ;
0637 
0638 }
0639 
0640 
0641 
0642 inline void SGLFW::numkey_pressed(unsigned _num, unsigned modifiers)
0643 {
0644     bool with_shift = SGLM_Modifiers::IsShift(modifiers) ;
0645     bool with_control = SGLM_Modifiers::IsControl(modifiers) ;
0646     bool with_alt = SGLM_Modifiers::IsAlt(modifiers) ;
0647     bool with_super = SGLM_Modifiers::IsSuper(modifiers) ;
0648 
0649     unsigned offset = 0 ;
0650     if(         with_shift && !with_alt)  offset = 10 ;
0651     else if(   !with_shift &&  with_alt)  offset = 20 ;
0652     else if(    with_shift &&  with_alt)  offset = 30 ;
0653 
0654     unsigned num = _num + offset ;
0655 
0656     if(level > 1) std::cout
0657         << "SGLFW::numkey_pressed"
0658         << " _num " << _num
0659         << " modifiers " << modifiers
0660         << " SGLM_Modifiers::Desc(modifiers) " << SGLM_Modifiers::Desc(modifiers)
0661         << " offset " << offset
0662         << " num " << num
0663         << " with_shift " << with_shift
0664         << " with_control " << with_control
0665         << " with_alt " << with_alt
0666         << " with_super " << with_super
0667         << "\n"
0668         ;
0669 
0670     set_wanted_frame_idx(num);
0671 }
0672 inline void SGLFW::set_wanted_frame_idx(int _idx){ wanted_frame_idx = _idx ; }
0673 inline int  SGLFW::get_wanted_frame_idx() const { return wanted_frame_idx ; }
0674 
0675 
0676 
0677 /**
0678 SGLFW::handle_frame_hop
0679 -------------------------
0680 
0681 When frame hop keypress detected pass the wanted frame idx to gm.
0682 The wanted_frame_idx starts as -2 until press a number key 0-9
0683 without or with modifiers shift/alt/shift+alt.
0684 The wanted_frame_idx is set back to -2 when press M
0685 for the MOI starting frame.
0686 
0687 **/
0688 
0689 
0690 inline void SGLFW::handle_frame_hop()
0691 {
0692     if(level > 0) std::cout << "SGLFW::handle_frame_hop\n" ;
0693     int _wanted_frame_idx = get_wanted_frame_idx() ;
0694     gm.handle_frame_hop(_wanted_frame_idx);
0695 }
0696 
0697 
0698 
0699 
0700 inline void SGLFW::snap(int w){ set_wanted_snap(w); }
0701 
0702 inline void SGLFW::set_wanted_snap(int w){ wanted_snap = w ; }
0703 inline int SGLFW::get_wanted_snap() const { return wanted_snap ; }
0704 
0705 
0706 /**
0707 SGLFW::handle_snap
0708 -------------------
0709 
0710 **/
0711 
0712 
0713 inline void SGLFW::handle_snap()
0714 {
0715     int _wanted_snap = get_wanted_snap();
0716     if( _wanted_snap == 1 || _wanted_snap == 2 )
0717     {
0718         std::cout << "SGLFW::handle_snap _wanted_snap " << _wanted_snap << "\n" ;
0719         bool yflip = _wanted_snap == 1 ;
0720         snap_local(yflip);
0721         set_wanted_snap(0);
0722     }
0723 }
0724 
0725 
0726 
0727 
0728 
0729 
0730 /**
0731 SGLFW::download_pixels
0732 --------------------------
0733 
0734 After oglrap Pix
0735 
0736 https://www.khronos.org/opengl/wiki/GLAPI/glPixelStore
0737 
0738 **/
0739 
0740 inline void SGLFW::download_pixels()
0741 {
0742     if(sif == nullptr) init_img_frame();
0743 
0744     assert( _width > 0 && _width == sif->width ) ;
0745     assert( _height > 0 && _height == sif->height ) ;
0746 
0747     glPixelStorei(GL_PACK_ALIGNMENT,1);   // byte aligned output
0748     glReadPixels(0,0,_width,_height,GL_RGBA, GL_UNSIGNED_BYTE, sif->pixels );
0749 
0750     if(sid != nullptr) download_depth();
0751 }
0752 
0753 
0754 /**
0755 SGLFW::download_depth
0756 ----------------------
0757 
0758 Depth component "native" type is GL_FLOAT in range 0. to 1.
0759 when type is not GL_FLOAT then the read values are scaled depending
0760 on the type, eg by 255. for type of GL_UNSIGNED_BYTE
0761 
0762 * https://registry.khronos.org/OpenGL-Refpages/gl4/html/glReadPixels.xhtml
0763 
0764 **/
0765 
0766 inline void SGLFW::download_depth()
0767 {
0768     assert(sid);
0769     GLenum format = GL_DEPTH_COMPONENT ;
0770     GLenum type = GL_UNSIGNED_BYTE ;
0771     glPixelStorei(GL_PACK_ALIGNMENT,1);   // byte aligned output
0772     glReadPixels(0,0,_width,_height, format, type, sid->pixels );
0773 }
0774 
0775 
0776 /**
0777 SGLFW::init_img_frame
0778 -------------------------
0779 
0780 export SGLFW__DEPTH=1
0781    enables download and saving of jpg depth maps together with ordinary screenshots
0782 
0783 **/
0784 
0785 
0786 inline void SGLFW::init_img_frame()
0787 {
0788     assert( _width > 0 );
0789     assert( _height > 0 );
0790     int channels = 4 ;
0791 
0792     sif = new SIMG_Frame(_width, _height, channels ) ;
0793 
0794     bool DEPTH  = ssys::getenvbool("SGLFW__DEPTH");
0795     if(DEPTH)
0796     {
0797         sid = new SIMG_Frame(_width, _height, 1 );
0798     }
0799 }
0800 inline void SGLFW::writeJPG(const char* path) const
0801 {
0802     std::cout << "SGLFW::writeJPG [" << ( path ? path : "-" ) << "]\n" ;
0803     assert(sif);
0804     sif->writeJPG(path);
0805 
0806     if(sid)
0807     {
0808         const char* dpath = sstr::ReplaceEnd( path, ".jpg", "_depth.jpg" );
0809         std::cout << "SGLFW::writeJPG [" << ( dpath ? dpath : "-" ) << "]\n" ;
0810         sid->writeJPG(dpath);
0811 
0812         const char* npath = sstr::ReplaceEnd( path, ".jpg", "_depth.npy" );
0813         std::cout << "SGLFW::writeNPY [" << ( npath ? npath : "-" ) << "]\n" ;
0814         sid->writeNPY(npath);
0815     }
0816 }
0817 
0818 
0819 /**
0820 SGLFW::snap_local
0821 -----------------
0822 
0823 Default stem of nullptr leads to use of current datetime formatted
0824 string of form sstamp::DEFAULT_TIME_FMT
0825 
0826 CAUTION : this is currently NOT used from::
0827 
0828      ~/o/cx.sh
0829      CSGOptiXRenderInteractiveTest
0830      CSGOptiX::snap
0831 
0832 It is used from::
0833 
0834    sysrap/tests/SGLFW_SOPTIX_Scene_test.cc
0835 
0836 **/
0837 
0838 inline void SGLFW::snap_local(bool yflip)
0839 {
0840     download_pixels();
0841     if(yflip)
0842     {
0843         sif->flipVertical();
0844         if(sid) sid->flipVertical();
0845     }
0846 
0847     const char* stem = ssys::getenvvar("SGLFW__snap_local_STEM", nullptr );
0848     int index = 0 ;
0849     const char* ext = ".jpg" ;
0850     bool unique = true ;
0851     const char* path = spath::DefaultOutputPath(stem, index, ext, unique);
0852     spath::MakeDirsForFile(path);
0853 
0854     writeJPG(path);
0855 }
0856 
0857 
0858 inline void SGLFW::key_repeated(unsigned key)
0859 {
0860     //std::cout << "SGLFW::key_repeated " << key << "\n" ;
0861     keys.key_pressed(key);
0862     unsigned modifiers = keys.modifiers() ;
0863     if(modifiers == 0) return ;
0864     gm.key_pressed_action(modifiers) ;
0865     gm.update();
0866 }
0867 
0868 inline void SGLFW::key_released(unsigned key)
0869 {
0870     keys.key_released(key);
0871 }
0872 
0873 inline void SGLFW::button_pressed(unsigned button, unsigned mods)
0874 {
0875     std::cout << "SGLFW::button_pressed UNHANDLED " << button << " " << mods << "\n" ;
0876 }
0877 inline void SGLFW::button_released(unsigned button, unsigned mods)
0878 {
0879     std::cout << "SGLFW::button_released UNHANDLED " << button << " " << mods << "\n" ;
0880 }
0881 
0882 
0883 /**
0884 SGLFW::command
0885 ---------------
0886 
0887 +--------+-------------------------+------------------------+
0888 |  key   | command                 | note                   |
0889 +========+=========================+========================+
0890 |   I    | --snap-local            |                        |
0891 +--------+-------------------------+------------------------+
0892 |   J    | --snap-local-inverted   |                        |
0893 +--------+-------------------------+------------------------+
0894 |   K    | --snap                  |                        |
0895 +--------+-------------------------+------------------------+
0896 |   L    | --snap-inverted         |                        |
0897 +--------+-------------------------+------------------------+
0898 
0899 Seems the difference between --snap-local and --snap
0900 is whether the snap is done immediately or slightly delayed
0901 via the wanted mechanism.  Suspect that in this case
0902 of direct OpenGL snapshots there is no benefit from the
0903 distinction. However for OptiX snapshots can imagine
0904 that this is needed for a check in the render loop
0905 to see the "notification" and do the OptiX snap without
0906 needing any dependency.
0907 
0908 **/
0909 
0910 
0911 inline int SGLFW::command(const char* cmd)
0912 {
0913     if(strcmp(cmd, "--exit") == 0) renderloop_exit();
0914     if(strcmp(cmd, "--help") == 0) Help();
0915     if(strcmp(cmd, "--home") == 0) home();
0916     if(strcmp(cmd, "--desc") == 0) _desc();
0917     if(strcmp(cmd, "--tcam") == 0) tcam();
0918     if(strcmp(cmd, "--snap") == 0) snap(1);
0919     if(strcmp(cmd, "--snap-inverted") == 0) snap(2);
0920     if(strcmp(cmd, "--snap-local") == 0) snap_local(false);
0921     if(strcmp(cmd, "--snap-local-inverted") == 0) snap_local(true);
0922     if(strcmp(cmd, "--traceyflip") == 0) traceyflip();
0923     if(strcmp(cmd, "--rendertype") == 0) rendertype();
0924     return 0 ;
0925 }
0926 
0927 inline void SGLFW::Help()
0928 {
0929     std::cout << HELP ;
0930 }
0931 
0932 /**
0933 SGLFW::home
0934 -------------
0935 
0936 1. center cursor position
0937 2. zero the eyeshift and rotations, set zoom to 1
0938 
0939 WIP: doing this from ctor at tail of SGLFW::init flaky
0940      so try from renderloop_head for count==0
0941 
0942 **/
0943 
0944 inline void SGLFW::home()
0945 {
0946     if(level > 0) std::cout << "SGLFW::home\n" ;
0947     setCursorPos_home();
0948     gm.command("--home");
0949 }
0950 inline void SGLFW::_desc()
0951 {
0952     gm.command("--desc");
0953 }
0954 inline void SGLFW::tcam()
0955 {
0956     gm.command("--tcam");
0957 }
0958 inline void SGLFW::traceyflip()
0959 {
0960     gm.command("--traceyflip");
0961 }
0962 inline void SGLFW::rendertype()
0963 {
0964     gm.command("--rendertype");
0965 }
0966 
0967 
0968 
0969 
0970 
0971 inline std::string SGLFW::FormCommand(const char* token, float value)  // static
0972 {
0973     std::stringstream ss ;
0974     ss << token << " " << value ;
0975     std::string str = ss.str();
0976     return str ;
0977 }
0978 
0979 
0980 
0981 
0982 /**
0983 SGLFW::getWindowSize
0984 ---------------------
0985 
0986 eg on macOS with retina screen : SGLFW::descWindowSize wh[1024, 768] _wh[2048,1536]
0987 
0988 **/
0989 inline void SGLFW::getWindowSize()
0990 {
0991     glfwGetWindowSize(window, &width, &height);
0992     glfwGetFramebufferSize(window, &_width, &_height);
0993 }
0994 inline std::string SGLFW::descWindowSize() const
0995 {
0996     std::stringstream ss ;
0997     ss << "SGLFW::descWindowSize"
0998        << " wh["
0999        << std::setw(4) << width
1000        << ","
1001        << std::setw(4) << height
1002        << "]"
1003        << " _wh["
1004        << std::setw(4) << _width
1005        << ","
1006        << std::setw(4) << _height
1007        << "]"
1008        ;
1009     std::string str = ss.str();
1010     return str ;
1011 }
1012 
1013 
1014 /**
1015 SGLFW::setCursorPos
1016 --------------------
1017 
1018 **/
1019 
1020 inline void SGLFW::setCursorPos(float ndc_x, float ndc_y )
1021 {
1022     float x = (1.f + ndc_x)*width/2.f ;
1023     float y = (1.f - ndc_y)*height/2.f ;
1024     glfwSetCursorPos(window, x, y );
1025 }
1026 
1027 inline void SGLFW::setCursorPos_home()
1028 {
1029     setCursorPos(0.f,0.f);
1030 }
1031 
1032 
1033 /**
1034 SGLFW::getStartPos
1035 ----------------------
1036 
1037 
1038 ::
1039 
1040     TL:cursor(0.,0.) ndc(-1.,1.)
1041     +-----------------+
1042     |                 |
1043     |                 |
1044     |        + CENTER: cursor(width/2,height/2) ndc(0.,0.)
1045     |                 |
1046     |                 |
1047     +-----------------+ BR:cursor(width, height) ndc(1.,-1.)
1048 
1049 
1050 **/
1051 
1052 inline void SGLFW::getStartPos()
1053 {
1054     glfwGetCursorPos(window, &_start_x, &_start_y );
1055 
1056     start_ndc.x = 2.f*_start_x/width - 1.f ;
1057     start_ndc.y = 1.f - 2.f*_start_y/height ;
1058 }
1059 
1060 /**
1061 SGLFW::cursor_moved
1062 -----------------------
1063 
1064 Follow old approach from Frame::cursor_moved_just_move
1065 
1066 1. convert pixel positions into ndc (x,y) [-1:1, -1:1]
1067 
1068 
1069 As cursor_moved gets called repeatedly during mouse
1070 movements the drag.z drag.w tend to be small.
1071 
1072 To combat this for local rotation control via quaternion
1073 use the abolute start position (from key_pressed)
1074 and current position from cursor_moved.
1075 
1076 
1077 
1078 WORKAROUND NON-REPRODUCIBLE START VIEWPOINT (depends on prior cursor position)
1079 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1080 
1081 At startup observe that SGLFW::cursor_moved is called before any intentional
1082 cursor movement causing SGLM::setEyeRotation to set a whacky unpredictable
1083 initial view orientation until press the H key which resets q_eyerot.
1084 Placing the cursor position in the center of the screen prior to launching
1085 the application leads to the initial position being close to the home position.
1086 So the problem can be restated as an unintended dependency of the the
1087 initial view on the position the cursor was left at prior to launching the app.
1088 
1089 Attempts to fix the cursor position to the center of the screen/window before
1090 there is any interpretation of cursor position failed to fix the issue
1091 of non-reproducible initial view. Maybe because are trying to change
1092 cursor position prior to that being possible.
1093 
1094 Find that ignoring the first few SGLFW::cursor_moved using
1095 simple *cursor_moved_count* avoids the issue of non-reproducible start
1096 viewpoint.
1097 
1098 **/
1099 inline void SGLFW::cursor_moved(int ix, int iy)
1100 {
1101     cursor_moved_count += 1 ;
1102     if(cursor_moved_count < 3)
1103     {
1104          // ad-hoc ignore first few cursor moves
1105          return ;
1106     }
1107 
1108     move_ndc.x  = 2.f*float(ix)/width - 1.f ;
1109     move_ndc.y  = 1.f - 2.f*float(iy)/height ;
1110 
1111     float dx = move_ndc.x - drag.x ;
1112     float dy = move_ndc.y - drag.y ;   // delta with the prior call to cursor_moved
1113 
1114     drag.x = move_ndc.x ;
1115     drag.y = move_ndc.y ;
1116     drag.z = dx ;
1117     drag.w = dy ;
1118 
1119     if(level > 1) std::cout
1120        << "SGLFW::cursor_moved (ix,iy)(" << ix << "," << iy << ")"
1121        << " cursor_moved_count " << cursor_moved_count
1122        << " start_ndc " << SGLM::Present(start_ndc)
1123        << " move_ndc " << SGLM::Present(move_ndc)
1124        << " drag " << SGLM::Present(drag)
1125        << "\n"
1126        ;
1127 
1128 
1129     cursor_moved_action();
1130 }
1131 
1132 
1133 /**
1134 SGLFW::cursor_moved_action
1135 ----------------------------
1136 
1137 HMM: potentially the adhoc sensitivity factors below should
1138 depend on the extent of the geometry and the time range of the
1139 event being rendered ?
1140 
1141 **/
1142 
1143 
1144 inline void SGLFW::cursor_moved_action()
1145 {
1146     unsigned modifiers = keys.modifiers() ;
1147     float dy = drag.w ;
1148     if(gm.toggle.zoom)
1149     {
1150         std::string cmd = FormCommand("--inc-zoom", dy*5 );
1151         gm.command(cmd.c_str()) ;
1152     }
1153     else if(gm.toggle.tmin)
1154     {
1155         std::string cmd = FormCommand("--inc-tmin", dy );
1156         gm.command(cmd.c_str()) ;
1157     }
1158     else if(gm.toggle.tmax)
1159     {
1160         std::string cmd = FormCommand("--inc-tmax", dy*100 );
1161         gm.command(cmd.c_str()) ;
1162     }
1163     else if(gm.toggle.time)
1164     {
1165         std::string cmd = FormCommand("--inc-time", dy );
1166         gm.command(cmd.c_str()) ;
1167     }
1168     /*
1169     else if(gm.toggle.lrot)
1170     {
1171         //std::cout << "SGLFW::cursor_moved_action.lrot " << std::endl ;
1172         gm.setLookRotation(start_ndc, move_ndc);
1173         gm.update();
1174     }
1175    */
1176     else if(!gm.toggle.stop)
1177     {
1178         gm.cursor_moved_action(start_ndc, move_ndc, modifiers );
1179         gm.update();
1180     }
1181 }
1182 
1183 inline std::string SGLFW::descDrag() const
1184 {
1185     std::stringstream ss ;
1186     ss
1187         << " ("
1188         << std::setw(10) << std::fixed << std::setprecision(3) << drag.x
1189         << ","
1190         << std::setw(10) << std::fixed << std::setprecision(3) << drag.y
1191         << ","
1192         << std::setw(10) << std::fixed << std::setprecision(3) << drag.z
1193         << ","
1194         << std::setw(10) << std::fixed << std::setprecision(3) << drag.w
1195         << ")"
1196         ;
1197     std::string str = ss.str();
1198     return str ;
1199 }
1200 
1201 
1202 inline std::string SGLFW::descStartPos() const
1203 {
1204     std::stringstream ss ;
1205     ss << "SGLFW::descCursorPos"
1206        << "["
1207        << std::setw(7) << std::fixed << std::setprecision(2) << _start_x
1208        << ","
1209        << std::setw(7) << std::fixed << std::setprecision(2) << _start_y
1210        << "]"
1211        << " ndc["
1212        << std::setw(7) << std::fixed << std::setprecision(2) << start_ndc.x
1213        << ","
1214        << std::setw(7) << std::fixed << std::setprecision(2) << start_ndc.y
1215        << "]"
1216        ;
1217     std::string str = ss.str();
1218     return str ;
1219 }
1220 
1221 /**
1222 SGLFW::SGLFW
1223 -------------
1224 
1225  -1:corresponds to frame formed from entire CE center-extent
1226 
1227 **/
1228 
1229 inline SGLFW::SGLFW(SGLM& _gm )
1230     :
1231     gm(_gm),
1232     level(ssys::getenvint("SGLFW_LEVEL", 0)),
1233     sleep_break(ssys::getenvint(_SLEEP_BREAK,0)),
1234     wanted_frame_idx(-2),  // -2:means the MOI frame
1235     wanted_snap(0),
1236     width(gm.Width()),
1237     height(gm.Height()),
1238     depth_test(gm.depthTest()),
1239     title(TITLE),
1240     window(nullptr),
1241     count(0),
1242     renderlooplimit(ssys::getenvint("SGLFW__renderlooplimit",1000000)),
1243     renderloop_tail_LEVEL(ssys::getenvint(SGLFW__renderloop_tail_LEVEL, 0)),
1244     exitloop(false),
1245     dump(false),
1246     _width(0),
1247     _height(0),
1248     sif(nullptr),
1249     sid(nullptr),
1250     _start_x(0.),
1251     _start_y(0.),
1252     cursor_moved_count(0),
1253     start_ndc(0.f,0.f),
1254     move_ndc(0.f,0.f),
1255     drag(0.f,0.f,0.f,0.f)
1256 {
1257     init();
1258 }
1259 
1260 
1261 
1262 
1263 
1264 
1265 inline SGLFW::~SGLFW()
1266 {
1267     glfwDestroyWindow(window);
1268     glfwTerminate();
1269 }
1270 
1271 inline void SGLFW::Error_callback(int error, const char* description) // static
1272 {
1273     fprintf(stderr, "SGLFW::Error_callback: %s\n", description);
1274 }
1275 
1276 /**
1277 SGLFW::init
1278 -------------
1279 
1280 1. OpenGL initialize
1281 2. create window
1282 
1283 
1284 Example responses::
1285 
1286      Renderer: NVIDIA GeForce GT 750M OpenGL Engine
1287      OpenGL version supported 4.1 NVIDIA-10.33.0 387.10.10.10.40.105
1288 
1289      Renderer: TITAN RTX/PCIe/SSE2
1290      OpenGL version supported 4.1.0 NVIDIA 418.56
1291 
1292 **/
1293 
1294 inline void SGLFW::init()
1295 {
1296     if(level > 1) printf("[SGLFW::init level %d \n", level);
1297     glfwSetErrorCallback(SGLFW::Error_callback);
1298     if (!glfwInit()) exit(EXIT_FAILURE);
1299 
1300     gleqInit();
1301 
1302 #if defined __APPLE__
1303     glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3);  // version specifies the minimum, not what will get on mac
1304     glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 2);
1305     glfwWindowHint (GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
1306     glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
1307 
1308 #elif defined _MSC_VER
1309     glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
1310     glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 1);
1311 
1312 #elif __linux
1313 
1314     if(level > 1) printf(".SGLFW::init.__linux\n");
1315     glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4);
1316     glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6);  // 1/6 ?
1317     glfwWindowHint (GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);  // remove stuff deprecated in requested release
1318     glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
1319     glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
1320     // https://learnopengl.com/In-Practice/Debugging Debug output is core since OpenGL version 4.3,
1321 #endif
1322 
1323 
1324     // HMM: using fullscreen mode with resolution less than display changes display resolution
1325     GLFWmonitor* monitor = gm.fullscreen ? glfwGetPrimaryMonitor() : nullptr ;   // nullptr for windowed mode
1326     GLFWwindow* share = nullptr ;     // window whose context to share resources with, or NULL to not share resources
1327 
1328     if(level > 1) printf(".SGLFW::init width %d height %d\n", width, height);
1329     window = glfwCreateWindow(width, height, title, monitor, share);
1330     if (!window)
1331     {
1332         glfwTerminate();
1333         exit(EXIT_FAILURE);
1334     }
1335     //glfwSetKeyCallback(window, SGLFW::key_callback);  // using gleq event for key callbacks not this manual approach
1336     glfwMakeContextCurrent(window);
1337 
1338 
1339     gleqTrackWindow(window);  // replaces callbacks, see https://github.com/glfw/gleq
1340 
1341     glewExperimental = GL_TRUE;
1342     glewInit ();
1343 
1344 
1345 
1346     GLenum err0 = glGetError() ;
1347     GLenum err1 = glGetError() ;
1348     bool err0_expected = err0 == GL_INVALID_ENUM ; // long-standing glew bug apparently
1349     bool err1_expected = err1 == GL_NO_ERROR ;
1350     if(!err0_expected) printf("//SGLFW::init UNEXPECTED err0 %d \n", err0 );
1351     if(!err1_expected) printf("//SGLFW::init UNEXPECTED err1 %d \n", err1 );
1352     //assert( err0_expected );
1353     //assert( err1_expected );
1354 
1355     const GLubyte* renderer = glGetString (GL_RENDERER);
1356     const GLubyte* version = glGetString (GL_VERSION);
1357 
1358     if(level > 0)
1359     {
1360         printf("//SGLFW::init GL_RENDERER [%s] \n", renderer );
1361         printf("//SGLFW::init GL_VERSION [%s] \n", version );
1362     }
1363 
1364 
1365     //  https://learnopengl.com/Advanced-OpenGL/Depth-testing
1366     if(depth_test == 0)
1367     {
1368         if(level > 1) printf("//SGLFW::init NOT-ENABLED depth_test %d \n", depth_test );
1369     }
1370     else
1371     {
1372         if(level > 1) printf("//SGLFW::init ENABLED depth_test %d \n", depth_test );
1373         glEnable(GL_DEPTH_TEST);
1374         glDepthFunc(GL_LESS);
1375     }
1376 
1377     glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); // otherwise gl_PointSize setting ignored, setting in geom not vert shader used when present
1378 
1379     int interval = 1 ; // The minimum number of screen updates to wait for until the buffers are swapped by glfwSwapBuffers.
1380     glfwSwapInterval(interval);
1381 
1382     //home(); // too soon to do this, as flaky where the cursor starts
1383     if(level > 1) printf("]SGLFW::init\n");
1384 }
1385 
1386 inline void SGLFW::setWindowTitle(const char* title)
1387 {
1388     glfwSetWindowTitle(window, title);
1389 }
1390 
1391 
1392