mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 05:52:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1710 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1710 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Hyperbolic Rogue -- VR support
 | |
| // Copyright (C) 2020-2020 Zeno Rogue, see 'hyper.cpp' for details
 | |
| 
 | |
| /** \file vr.cpp
 | |
|  *  \brief VR support
 | |
|  */
 | |
| 
 | |
| #include "hyper.h"
 | |
| namespace hr {
 | |
| 
 | |
| EX namespace vrhr {
 | |
| 
 | |
| #if !CAP_VR
 | |
| #if HDR
 | |
| inline bool active() { return false; }
 | |
| inline bool rendering() { return false; }
 | |
| inline bool rendering_eye() { return false; }
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
| #if CAP_VR
 | |
| 
 | |
| /** VR is active */
 | |
| EX bool active() { return state; }
 | |
| /** called in drawqueue to see if we should switch to vrhr::render() */
 | |
| EX bool should_render() { return state == 1; }
 | |
| /** currently rendering a VR-aware screen */
 | |
| EX bool rendering() { return state == 2 || state == 4; }
 | |
| /** currently rendering a VR eye */
 | |
| EX bool rendering_eye() { return state == 2; }
 | |
| 
 | |
| #if HDR
 | |
| enum class eHeadset { none, rotation_only, reference, holonomy, model_viewing, holonomy_z };
 | |
| enum class eEyes { none, equidistant, truesim };
 | |
| enum class eCompScreen { none, reference, single, eyes };
 | |
| #endif
 | |
| 
 | |
| EX eHeadset hsm = eHeadset::reference;
 | |
| EX eEyes eyes = eEyes::equidistant;
 | |
| EX eCompScreen cscr = eCompScreen::eyes;
 | |
| 
 | |
| EX bool controllers_on_desktop = true;
 | |
| 
 | |
| EX cell *forward_cell;
 | |
| 
 | |
| EX ld vraim_x, vraim_y, vrgo_x, vrgo_y;
 | |
| 
 | |
| EX ld pointer_length = 1;
 | |
| 
 | |
| vector<pair<string, string> > headset_desc = {
 | |
|   {"none", "Ignore the headset movement and rotation."},
 | |
|   {"rotation only", "Ignore the headset movement but do not ignore its rotation."},
 | |
|   {"reference", "The reference point in the real world corresponds to the reference point in VR. When you move your head in a loop, you return to where you started."},
 | |
|   {"holonomy", "Headsets movements in the real world are translated to the same movements in VR. Since the geometry is different, when you move your head in a loop, you usually don't return "
 | |
|    "to where you started."},
 | |
|   {"view model", "Fix a 3D projection of the non-Euclidean world, and see it from many viewpoints."},
 | |
|   {"holonomy Z", "in 2D geometries rendered in 3D, like holonomy, but keep the correct altitude and vertical direction."},
 | |
|   };
 | |
| 
 | |
| vector<pair<string, string> > eyes_desc = {
 | |
|   {"none", "Both eyes see the same image."},
 | |
|   {"equidistant", "Render the image so that the perceived direction and distance is correct."},
 | |
|   {"true vision", "Simulate the actual binocular vision in the non-Euclidean space. Hyperbolic spaces look smaller than they are (stretched Klein model), spherical spaces look weird, "
 | |
|     "nonisotropic spaces are incomprehensible."}, /* not implemented */
 | |
|   };
 | |
| 
 | |
| /* not implemented */
 | |
| vector<pair<string, string> > comp_desc = {
 | |
|   {"none", "Do not display anything on the computer screen."},
 | |
|   {"reference", "Display the standard HyperRogue view from the reference point."},
 | |
|   {"single", "Display a a single monocular image from the headset."},
 | |
|   {"eyes", "Display a copy of the VR display."},
 | |
|   };
 | |
| 
 | |
| struct vr_rendermodel {
 | |
|   string name;
 | |
|   GLuint texture_id;
 | |
|   vector<glhr::textured_vertex> vertices;
 | |
|   };
 | |
| 
 | |
| struct vr_framebuffer {
 | |
|   bool ok;
 | |
|   GLuint m_nDepthBufferId;
 | |
|   GLuint m_nRenderTextureId;
 | |
|   GLuint m_nRenderFramebufferId;
 | |
|   GLuint m_nResolveTextureId;
 | |
|   GLuint m_nResolveFramebufferId;
 | |
|   vr_framebuffer(int x, int y);
 | |
|   ~vr_framebuffer();
 | |
|   };
 | |
| 
 | |
| vr_framebuffer::vr_framebuffer(int xsize, int ysize) {
 | |
|   resetbuffer rb;
 | |
|   glGenFramebuffers(1, &m_nRenderFramebufferId );
 | |
|   glBindFramebuffer(GL_FRAMEBUFFER, m_nRenderFramebufferId);
 | |
| 
 | |
|   glGenRenderbuffers(1, &m_nDepthBufferId);
 | |
|   glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBufferId);
 | |
|   glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, xsize, ysize );
 | |
|   glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_nDepthBufferId );
 | |
|   glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_nDepthBufferId );
 | |
| 
 | |
|   glGenTextures(1, &m_nRenderTextureId );
 | |
|   glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_nRenderTextureId );
 | |
|   glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, xsize, ysize, true);
 | |
|   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_nRenderTextureId, 0);
 | |
| 
 | |
|   glGenFramebuffers(1, &m_nResolveFramebufferId );
 | |
|   glBindFramebuffer(GL_FRAMEBUFFER, m_nResolveFramebufferId);
 | |
| 
 | |
|   glGenTextures(1, &m_nResolveTextureId );
 | |
|   glBindTexture(GL_TEXTURE_2D, m_nResolveTextureId );
 | |
|   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | |
|   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
 | |
|   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, xsize, ysize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
 | |
|   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_nResolveTextureId, 0);
 | |
| 
 | |
|   // check FBO status
 | |
|   GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 | |
|   
 | |
|   ok = status == GL_FRAMEBUFFER_COMPLETE;
 | |
|   
 | |
|   rb.reset();
 | |
|   }
 | |
| 
 | |
| EX transmatrix eyeproj, eyeshift;
 | |
| 
 | |
| vr_framebuffer::~vr_framebuffer() {
 | |
|   glDeleteRenderbuffers( 1, &m_nDepthBufferId );
 | |
|   glDeleteTextures( 1, &m_nRenderTextureId );
 | |
|   glDeleteFramebuffers( 1, &m_nRenderFramebufferId );
 | |
|   glDeleteTextures( 1, &m_nResolveTextureId );
 | |
|   glDeleteFramebuffers( 1, &m_nResolveFramebufferId );
 | |
|   }
 | |
| 
 | |
| struct controller_data {
 | |
|   int x, y, clicked;
 | |
|   };
 | |
| 
 | |
| struct vrdata_t {
 | |
|   vr::IVRSystem *vr;
 | |
|   uint32_t xsize, ysize;
 | |
|   vr_framebuffer *eyes[2];
 | |
|   transmatrix proj[2];
 | |
|   transmatrix iproj[2]; /* inverse of proj */
 | |
|   transmatrix eyepos[2];
 | |
|   vr::TrackedDevicePose_t poses[ vr::k_unMaxTrackedDeviceCount ];
 | |
|   transmatrix pose_matrix[vr::k_unMaxTrackedDeviceCount ];
 | |
|   transmatrix last_pose_matrix[ vr::k_unMaxTrackedDeviceCount ];
 | |
|   vector<vr_rendermodel*> models;
 | |
|   vr_rendermodel* device_models[ vr::k_unMaxTrackedDeviceCount ];
 | |
|   controller_data cdata [ vr::k_unMaxTrackedDeviceCount ];
 | |
|   };
 | |
| 
 | |
| /** 0,1 == eyes, 2 == headset */
 | |
| EX transmatrix hmd_mv_for[3];
 | |
| EX transmatrix hmd_pre_for[3];
 | |
| 
 | |
| vrdata_t vrdata;
 | |
| 
 | |
| /** how far is the object pointed to */
 | |
| EX ld pointer_distance;
 | |
| 
 | |
| /** should we try to access VR */
 | |
| EX bool enabled = false;
 | |
| 
 | |
| /** we tried to access VR but failed */
 | |
| EX bool failed;
 | |
| 
 | |
| /** VR error message */
 | |
| EX string error_msg;
 | |
| 
 | |
| /** 0 = not loaded, 1 = loaded but not currently rendering, 2 = currently rendering the VR screen, 3 = currently rendering the reference computer screen, 4 = currently rendering the single computer screen */
 | |
| EX int state = 0;
 | |
| 
 | |
| #if HDR
 | |
| // use E4 when working with real-world matrices to ensure that inverses, multiplications, etc. are computed correctly
 | |
| #define E4 dynamicval<eGeometry> g(geometry, gCubeTiling)
 | |
| #endif
 | |
| 
 | |
| #define IN_E4(x) [&]{ E4; return x; }()
 | |
| 
 | |
| std::string GetTrackedDeviceString( vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = NULL ) {
 | |
|   uint32_t unRequiredBufferLen = vr::VRSystem()->GetStringTrackedDeviceProperty( unDevice, prop, NULL, 0, peError );
 | |
|   if( unRequiredBufferLen == 0 ) return "";
 | |
| 
 | |
|   char *pchBuffer = new char[ unRequiredBufferLen ];
 | |
|   unRequiredBufferLen = vr::VRSystem()->GetStringTrackedDeviceProperty( unDevice, prop, pchBuffer, unRequiredBufferLen, peError );
 | |
|   std::string sResult = pchBuffer;
 | |
|   delete [] pchBuffer;
 | |
|   return sResult;
 | |
|   }
 | |
| 
 | |
| transmatrix vr_to_hr(vr::HmdMatrix44_t mat) {
 | |
|   transmatrix T;
 | |
|   for(int i=0; i<4; i++)
 | |
|   for(int j=0; j<4; j++)
 | |
|     T[i][j] = mat.m[i][j];
 | |
|   return T;
 | |
|   }
 | |
| 
 | |
| transmatrix vr_to_hr(vr::HmdMatrix34_t mat) {
 | |
|   transmatrix T;
 | |
|   for(int i=0; i<3; i++)
 | |
|   for(int j=0; j<4; j++)
 | |
|     T[i][j] = mat.m[i][j];
 | |
|   T[3][0] = 0;
 | |
|   T[3][1] = 0;
 | |
|   T[3][2] = 0;
 | |
|   T[3][3] = 1;
 | |
|   return T;
 | |
|   }
 | |
| 
 | |
| string device_class_name(vr::ETrackedDeviceClass v) {
 | |
|   if(v == vr::TrackedDeviceClass_Controller)
 | |
|     return "controller";
 | |
|   if(v == vr::TrackedDeviceClass_HMD)
 | |
|     return "HMD";
 | |
|   if(v == vr::TrackedDeviceClass_Invalid)
 | |
|     return "invalid";
 | |
|   if(v == vr::TrackedDeviceClass_GenericTracker)
 | |
|     return "tracker";
 | |
|   if(v == vr::TrackedDeviceClass_TrackingReference)
 | |
|     return "reference";
 | |
|   return "unknown";
 | |
|   }
 | |
| 
 | |
| EX bool first = true;
 | |
| 
 | |
| EX transmatrix hmd_at_ui = Id;
 | |
| EX transmatrix hmd_at = Id;
 | |
| EX transmatrix hmd_ref_at = Id;
 | |
| 
 | |
| EX transmatrix hmd_mvp, hmd_pre, hmd_mv;
 | |
| 
 | |
| EX transmatrix sm;
 | |
| 
 | |
| EX int ui_xmin, ui_ymin, ui_xmax, ui_ymax;
 | |
| 
 | |
| EX reaction_t change_ui_bounds;
 | |
| 
 | |
| #if HDR
 | |
| struct frustum_info {
 | |
|   transmatrix pre;
 | |
|   transmatrix nlp;
 | |
|   bool screen;
 | |
|   transmatrix proj;
 | |
|   };
 | |
| #endif
 | |
| 
 | |
| EX vector<frustum_info> frusta;
 | |
| 
 | |
| EX void set_ui_bounds() {
 | |
|   ui_xmin = 0;
 | |
|   ui_ymin = 0;
 | |
|   ui_xmax = current_display->xsize;
 | |
|   ui_ymax = current_display->ysize;  
 | |
|   if(change_ui_bounds)
 | |
|     change_ui_bounds();
 | |
|   }
 | |
| 
 | |
| EX void size_and_draw_ui_box() {
 | |
|   if(!vrhr::active()) return;
 | |
|   if(!vrhr::in_menu()) return;
 | |
| 
 | |
|   vrhr::set_ui_bounds();
 | |
|   color_t col = 0x000000C0;
 | |
|   current_display->next_shader_flags = 0;
 | |
|   dynamicval<eModel> m(pmodel, mdPixel);
 | |
|   
 | |
|   vrhr::in_vr_ui([&] {
 | |
|     glhr::color2(col);
 | |
|     glhr::set_depthtest(false);
 | |
|     vector<glvertex> vs;
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmin, ui_ymin, 0));
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmax, ui_ymin, 0));
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmax, ui_ymax, 0));
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmin, ui_ymin, 0));
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmin, ui_ymax, 0));
 | |
|     vs.emplace_back(glhr::makevertex(ui_xmax, ui_ymax, 0));
 | |
|     glhr::current_vertices = NULL;
 | |
|     glhr::vertices(vs);
 | |
|     glDrawArrays(GL_TRIANGLES, 0, 6);
 | |
|     });
 | |
|   }
 | |
| 
 | |
| vr_rendermodel *get_render_model(string name) {
 | |
|   for(auto& m: vrdata.models)
 | |
|     if(m->name == name)
 | |
|       return m;
 | |
|     
 | |
|   println(hlog, "trying to load model ", name);
 | |
|   
 | |
|   vr::RenderModel_t *pModel;
 | |
|   vr::EVRRenderModelError error;
 | |
|   while(1) {
 | |
|     error = vr::VRRenderModels()->LoadRenderModel_Async(name.c_str(), &pModel );
 | |
|     if(error != vr::VRRenderModelError_Loading) break;
 | |
|     usleep(1000);
 | |
|     }
 | |
|   if(error != vr::VRRenderModelError_None) {
 | |
|     println(hlog, "Unable to load render model %s - %s\n", name, vr::VRRenderModels()->GetRenderModelErrorNameFromEnum( error ) );
 | |
|     return NULL;
 | |
|     }
 | |
| 
 | |
|   vr::RenderModel_TextureMap_t *pTexture;
 | |
|   while (1) {
 | |
|     error = vr::VRRenderModels()->LoadTexture_Async( pModel->diffuseTextureId, &pTexture );
 | |
|     if(error != vr::VRRenderModelError_Loading) break;
 | |
|     usleep(1000);
 | |
|     }
 | |
|   if(error != vr::VRRenderModelError_None) {
 | |
|     println(hlog, "Unable to load render texture id:%d for render model %s\n", pModel->diffuseTextureId, name);
 | |
|     vr::VRRenderModels()->FreeRenderModel( pModel );
 | |
|     return NULL; // move on to the next tracked device
 | |
|     }
 | |
| 
 | |
|   auto md = new vr_rendermodel;
 | |
|   vrdata.models.emplace_back(md);
 | |
|   md->name = name;
 | |
|   
 | |
|   int cnt = pModel->unTriangleCount * 3;
 | |
|   for(int i=0; i<cnt; i++) {
 | |
|     glhr::textured_vertex tv;
 | |
|     int id = pModel->rIndexData[i];
 | |
|     for(int j=0; j<3; j++)
 | |
|       tv.coords[j] = pModel->rVertexData[id].vPosition.v[j];
 | |
|     tv.coords[3] = 1;
 | |
|     for(int j=0; j<2; j++)
 | |
|       tv.texture[j] = pModel->rVertexData[id].rfTextureCoord[j];
 | |
|     md->vertices.push_back(tv);
 | |
|     }
 | |
| 
 | |
|   glGenTextures(1, &md->texture_id);
 | |
|   glBindTexture( GL_TEXTURE_2D, md->texture_id);
 | |
| 
 | |
|   glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, pTexture->unWidth, pTexture->unHeight,
 | |
|     0, GL_RGBA, GL_UNSIGNED_BYTE, pTexture->rubTextureMapData );
 | |
| 
 | |
|   glGenerateMipmap(GL_TEXTURE_2D);
 | |
| 
 | |
|   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
 | |
|   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
 | |
|   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
 | |
|   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
 | |
| 
 | |
|   GLfloat fLargest;
 | |
|   glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest );
 | |
|   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest );
 | |
| 
 | |
|   glBindTexture( GL_TEXTURE_2D, 0 );
 | |
|   
 | |
|   println(hlog, "model loaded successfully");
 | |
|   return md;
 | |
|   }
 | |
| 
 | |
| EX bool need_poses = true;
 | |
| 
 | |
| void track_poses() {
 | |
|   E4;
 | |
| 
 | |
|   if(!need_poses) return;
 | |
|   need_poses = false;
 | |
|   vr::VRCompositor()->WaitGetPoses(vrdata.poses, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
 | |
|   // println(hlog, "poses received");
 | |
|   
 | |
|   for(int i=0; i<(int)vr::k_unMaxTrackedDeviceCount; i++) {
 | |
|     auto& p = vrdata.poses[i];
 | |
|     vrdata.device_models[i] = nullptr;
 | |
|     if(!p.bPoseIsValid)
 | |
|       continue;
 | |
|     transmatrix T = vr_to_hr(p.mDeviceToAbsoluteTracking) * sm;
 | |
|     
 | |
|     // println(hlog, "found ", device_class_name(vrdata.vr->GetTrackedDeviceClass(i)), " at ", T);
 | |
|     
 | |
|     vrdata.pose_matrix[i] = T;
 | |
| 
 | |
|     if(i == vr::k_unTrackedDeviceIndex_Hmd) {
 | |
|       hmd_at = inverse(T);
 | |
|       if(first) hmd_ref_at = hmd_at, first = false;
 | |
|       }
 | |
|     
 | |
|     auto& cd = vrdata.cdata[i];
 | |
|     cd.x = cd.y = 0;
 | |
| 
 | |
|     if(vrdata.vr->GetTrackedDeviceClass(i) == vr::TrackedDeviceClass_Controller) {
 | |
|       string mname = GetTrackedDeviceString(i, vr::Prop_RenderModelName_String );
 | |
|       vrdata.device_models[i] = get_render_model(mname);
 | |
|       
 | |
|       /*
 | |
|       cd.last = cd.cur;
 | |
|       bool ok = vrdata.vr->GetControllerState(i, &cd.cur, sizeof(state));
 | |
|       if(ok) {
 | |
|         println(hlog, "pressed = ", color_t(cd.cur.ulButtonPressed), " touched = ", color_t(cd.cur.ulButtonTouched), " on ", i);
 | |
|         for(int i=0; i<5; i++)
 | |
|           if(cd.cur.rAxis[i].x || cd.cur.rAxis[i].y)
 | |
|             println(hlog, "axis ", i, " = ", tie(cd.cur.rAxis[i].x, cd.cur.rAxis[i].y));
 | |
|         }
 | |
|       */
 | |
|       
 | |
|       if(which_pointer == i) {
 | |
|         if(in_menu()) {
 | |
|           hyperpoint h1 = sm * hmd_at_ui * vrdata.pose_matrix[i] * sm * C0;
 | |
|           hyperpoint h2 = sm * hmd_at_ui * vrdata.pose_matrix[i] * sm * point31(0, 0, -0.01);
 | |
|           ld p = ilerp(h1[2], h2[2], -ui_depth);
 | |
|           hyperpoint pxo = lerp(h1, h2, p);
 | |
|           hyperpoint px = pxo;
 | |
|           px[0] /= ui_size * ui_size_unit;
 | |
|           px[1] /= -ui_size * ui_size_unit;
 | |
|           px[0] += (ui_xmin+ui_xmax) / 2;
 | |
|           px[1] += (ui_ymin+ui_ymax) / 2;
 | |
|           targeting_menu = px[0] >= 0 && px[1] >= 0 && px[0] <= vid.xres && px[1] <= vid.yres;
 | |
|           if(targeting_menu) {
 | |
|             mousex = px[0];
 | |
|             mousey = px[1];
 | |
|             pointer_distance = hdist(pxo, h1);
 | |
|             }
 | |
|           }
 | |
|         else targeting_menu = false;
 | |
|         }
 | |
|       
 | |
|       if(hdist(vrdata.pose_matrix[i] * C0, vrdata.last_pose_matrix[i] * C0) > .05) {
 | |
|         vrdata.last_pose_matrix[i] = vrdata.pose_matrix[i];
 | |
|         mousing = true;
 | |
|         which_pointer = i;
 | |
|         println(hlog, "setting which_pointer to ", i);
 | |
|         }
 | |
|       }
 | |
|     }    
 | |
| 
 | |
|   if(!in_menu()) hmd_at_ui = hmd_at;
 | |
|   }
 | |
| 
 | |
| EX bool targeting_menu;
 | |
| 
 | |
| EX void send_click() {
 | |
|   holdmouse = false;
 | |
|   fix_mouseh();
 | |
|   println(hlog, "sending a click, getcstat = ", getcstat, " in menu = ", in_menu());
 | |
|   if(in_menu() && targeting_menu)
 | |
|     handlekey(getcstat, getcstat);
 | |
|   else
 | |
|     handlekey('-', '-');
 | |
|   }
 | |
| 
 | |
| EX void send_release() {
 | |
|   holdmouse = false;
 | |
|   fix_mouseh();
 | |
|   println(hlog, "sending a release");
 | |
|   handlekey(PSEUDOKEY_RELEASE, PSEUDOKEY_RELEASE);
 | |
|   }
 | |
|   
 | |
| EX void vr_control() {
 | |
|   if(!enabled || !vid.usingGL) {
 | |
|     if(state) shutdown_vr();
 | |
|     return;
 | |
|     }
 | |
|   if(enabled && vid.usingGL && !state && !failed) {
 | |
|     start_vr();
 | |
|     }
 | |
|   if(state == 1) {
 | |
|     track_actions();
 | |
|     need_poses = true;
 | |
|     }
 | |
|   static bool last_vr_clicked = false;
 | |
|   
 | |
|   shiftmul = getcshift;
 | |
|   
 | |
|   if(which_pointer) mousemoved = true;
 | |
| 
 | |
|   if(vr_clicked && last_vr_clicked && holdmouse) send_click();
 | |
|   
 | |
|   mousepressed = vr_clicked;
 | |
| 
 | |
|   if(vr_clicked && !last_vr_clicked && vid.quickmouse) send_click();
 | |
| 
 | |
|   if(vr_clicked && !last_vr_clicked && !vid.quickmouse)
 | |
|     actonrelease = true;
 | |
| 
 | |
|   if(!vr_clicked && last_vr_clicked && !vid.quickmouse && actonrelease) {
 | |
|     send_click();
 | |
|     actonrelease = false;
 | |
|     }
 | |
| 
 | |
|   else if(!vr_clicked && last_vr_clicked) {
 | |
|     send_release();
 | |
|     }
 | |
| 
 | |
|   if(mousepressed && inslider) {
 | |
|     send_click();
 | |
|     }
 | |
|   
 | |
|   last_vr_clicked = vr_clicked;
 | |
|   }
 | |
| 
 | |
| EX void be_33(transmatrix& T) {
 | |
|   for(int i=0; i<3; i++) T[i][3] = T[3][i] = 0;
 | |
|   T[3][3] = 1;
 | |
|   }
 | |
| 
 | |
| eShiftMethod smVR() {
 | |
|   if(gproduct) return smProduct;
 | |
|   if(!nisot::geodesic_movement) return smLie;
 | |
|   if(nonisotropic || stretch::in()) return smGeodesic;
 | |
|   return smIsotropic;
 | |
|   }
 | |
| 
 | |
| EX void apply_movement(const transmatrix& rel, eShiftMethod sm) {
 | |
|   hyperpoint h0 = IN_E4(inverse(rel) * C0);
 | |
|   hyperpoint h = h0;
 | |
|   for(int i=0; i<3; i++) h[i] /= -absolute_unit_in_meters;
 | |
|   
 | |
|   shift_view(h, sm);
 | |
|   transmatrix Rot = rel;
 | |
|   be_33(Rot);
 | |
|   rotate_view(Rot);
 | |
|   }
 | |
| 
 | |
| EX void vr_shift() {
 | |
|   if(first) return;
 | |
|   rug::using_rugview urv;
 | |
|   if(GDIM == 2) return;
 | |
| 
 | |
|   auto hsm1 = hsm;
 | |
|   if(hsm1 == eHeadset::holonomy_z && !embedded_plane) hsm1 = eHeadset::holonomy;
 | |
|        
 | |
|   if(hsm1 == eHeadset::holonomy) {
 | |
|     apply_movement(IN_E4(hmd_at * inverse(hmd_ref_at)), smVR());
 | |
|     hmd_ref_at = hmd_at;
 | |
|     playermoved = false;
 | |
|     if(!rug::rugged) optimizeview();
 | |
|     }
 | |
| 
 | |
|   if(hsm1 == eHeadset::holonomy_z) {
 | |
| 
 | |
|     apply_movement(IN_E4(hmd_at * inverse(hmd_ref_at)), smEmbedded);
 | |
|     hmd_ref_at = hmd_at;
 | |
|     playermoved = false;
 | |
| 
 | |
|     bool below = cgi.WALL < cgi.FLOOR;
 | |
| 
 | |
|     if(vid.fixed_yz) {
 | |
|       transmatrix spin_T;
 | |
|       ld eye_level;
 | |
| 
 | |
|       if(1) {
 | |
|         dynamicval<eGeometry> g(geometry, gCubeTiling);
 | |
|         spin_T = vrhr::hmd_at;
 | |
|         spin_T = vrhr::sm * inverse(spin_T);
 | |
|         eye_level = -spin_T[1][3] / vrhr::absolute_unit_in_meters;
 | |
|         vrhr::be_33(spin_T);
 | |
|         }
 | |
|       // auto shift = vrhr::sm * (inverse(hmd_at) * C0 - inverse(hmd_ref_at) * C0);
 | |
| 
 | |
|       hyperpoint h = tC0(view_inverse(actual_view_transform * View));
 | |
|       auto lcur = cgi.emb->get_logical_z(h);
 | |
|       auto lnew = cgi.FLOOR + (below?-1:1) * eye_level;
 | |
|       println(hlog, "lcur = ", lcur, " lnew = ", lnew, " below = ", below);
 | |
| 
 | |
|       if(1) {
 | |
|         hyperpoint p = Hypc;
 | |
|         p[1] = lcur - lnew;
 | |
|         p = hmd_ref_at * p;
 | |
|         if(below) p = -1 * p;
 | |
|         shift_view(p, smVR());
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     if(!rug::rugged) optimizeview();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX ld absolute_unit_in_meters = 3;
 | |
| 
 | |
| /** what point and cell is the controller number id pointing to */
 | |
| 
 | |
| EX eModel pmodel_3d_version() {
 | |
|   if(pmodel == mdGeodesic) return mdEquidistant;
 | |
|   if(pmodel == mdPerspective) return nonisotropic ? mdHorocyclic : mdEquidistant;
 | |
|   return pmodel;
 | |
|   }
 | |
| 
 | |
| /** convert model coordinates to controller-relative coordinates */
 | |
| EX transmatrix model_to_controller(int id) {
 | |
|   return inverse(sm * hmd_at * vrdata.pose_matrix[id] * sm) * hmd_mv;
 | |
|   }
 | |
| 
 | |
| EX hyperpoint model_location(shiftpoint h, bool& bad) {
 | |
|   if(eyes == eEyes::truesim) {
 | |
|   
 | |
|     hyperpoint eye_at[2], tangent[2];
 | |
|     bad = false;
 | |
|     for(int i=0; i<2; i++) {
 | |
|       shiftpoint h1 = h;
 | |
|       h1.h = hmd_pre_for[i] * h1.h;
 | |
|       eModel md = pmodel_3d_version();
 | |
|       hyperpoint hscr;
 | |
|       apply_other_model(h1, hscr, md);
 | |
|       E4;      
 | |
|       hscr[3] = 1;
 | |
|       eye_at[i] = vrdata.eyepos[i] * C0;
 | |
|       tangent[i] = vrdata.eyepos[i] * sm * hmd_mv_for[i] * (hscr - C0);
 | |
|       }
 | |
| 
 | |
|     // eye_at[0] + tangent[0] * a == eye_at[1] + tangent[1] * b
 | |
|     // (in coordinates 0,2; in nonisotropic geometries, [1] may be different)
 | |
|     
 | |
|     E4;
 | |
|     
 | |
|     auto t10 = tangent[1][0];
 | |
|     auto t12 = tangent[1][2];
 | |
|     auto t00 = tangent[0][0];
 | |
|     auto t02 = tangent[0][2];
 | |
|     
 | |
|     ld a = (t10 * eye_at[0][2] - t12 * eye_at[0][0] - t10 * eye_at[1][2] + t12 * eye_at[1][0]) / (t00 * t12 - t02 * t10);
 | |
|     ld b = (t00 * eye_at[1][2] - t02 * eye_at[1][0] - t00 * eye_at[0][2] + t02 * eye_at[0][0]) / (t10 * t02 - t12 * t00);
 | |
|     
 | |
|     hyperpoint hit0 = eye_at[0] + tangent[0] * a;
 | |
|     hyperpoint hit1 = eye_at[1] + tangent[1] * b;
 | |
|     // we should have hit0 == hit1, except coordinate [1]
 | |
| 
 | |
|     return (hit0 + hit1) / 2;
 | |
|     }
 | |
|   else {
 | |
|     hyperpoint hscr;
 | |
|     h.h = hmd_pre_for[2] * h.h;
 | |
|     eModel md = pmodel_3d_version();
 | |
|     apply_other_model(h, hscr, md);
 | |
|     bad = in_vr_sphere && get_side(hscr) == (sphere_flipped ? -1 : 1);
 | |
|   
 | |
|     hscr[3] = 1;
 | |
|     return hscr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| ld vr_distance(const shiftpoint& h, int id, ld& dist) {
 | |
| 
 | |
|   bool bad;
 | |
|   hyperpoint hscr = model_location(h, bad);
 | |
|   if(bad) return 1e5;
 | |
|   bool flat = WDIM == 2;
 | |
|   E4; hyperpoint hc = model_to_controller(id) * hscr;
 | |
|   if(flat) {
 | |
|     if(hc[2] > 0.1) return 1e6; /* behind */
 | |
|     dist = -hc[2];
 | |
|     return sqhypot_d(2, hc);
 | |
|     }
 | |
|   else {
 | |
|     hc[2] += dist;
 | |
|     return sqhypot_d(3, hc);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX hyperpoint vr_direction;
 | |
| 
 | |
| EX void compute_vr_direction(int id) {
 | |
|   E4;
 | |
|   transmatrix T = (hsm == eHeadset::none ? hmd_at : hmd_ref_at) * vrdata.pose_matrix[id] * sm;
 | |
|   vrhr::be_33(T);
 | |
|   vr_direction = T * point31(0, 0, -0.01);
 | |
|   }
 | |
| 
 | |
| EX void compute_point(int id, shiftpoint& res, cell*& c, ld& dist) {
 | |
| 
 | |
|   if(WDIM == 3) {
 | |
|     compute_vr_direction(id);
 | |
|     movedir md = vectodir(vr_direction);
 | |
|     cellwalker xc = cwt + md.d + wstep;
 | |
|     forward_cell = xc.at;
 | |
|     }
 | |
|   
 | |
|   dist = pointer_length;
 | |
| 
 | |
|   gen_mv();
 | |
|   set_vr_sphere();
 | |
|   c = nullptr;
 | |
|   ld best = 1e9;
 | |
|   
 | |
|   shiftmatrix T;
 | |
|   
 | |
|   // needed so that applymodel gives the VR coordinates
 | |
|   dynamicval<int> dvs (vrhr::state, 2); 
 | |
|   
 | |
|   for(auto p: current_display->all_drawn_copies) {
 | |
|     for(auto& V: p.second) {
 | |
|       shiftpoint h = V * pointable();
 | |
|       ld d = vr_distance(h, id, dist);
 | |
|       if(d < best) best = d, c = p.first, T = V;
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   auto rel = pointable();
 | |
|   
 | |
|   T = minimize_point_value(T, [&] (const shiftmatrix& T1) { return vr_distance(T1*rel, id, dist); });
 | |
|   
 | |
|   res = T * rel;
 | |
|   }  
 | |
| 
 | |
| EX bool vr_clicked;
 | |
| 
 | |
| void move_according_to(vr::ETrackedControllerRole role, bool last, bool cur) {
 | |
|   if(cur) vr_clicked = true;
 | |
|   int id = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(role);
 | |
|   if((last || cur) && id >= 0 && id < int(vr::k_unMaxTrackedDeviceCount)) {
 | |
|     println(hlog, "click setting which_pointer to ", id);
 | |
|     which_pointer = id;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| struct digital_action_data {
 | |
|   string action_name;
 | |
|   vr::VRActionHandle_t handle;
 | |
|   bool last, curr;
 | |
|   function<void(bool, bool)> act;
 | |
|   bool_reaction_t when;
 | |
|   digital_action_data(string s, bool_reaction_t when, function<void(bool, bool)> f) : when(when) { action_name = s; act = f; handle = vr::k_ulInvalidActionHandle; }
 | |
|   };
 | |
| 
 | |
| struct analog_action_data {
 | |
|   string action_name;
 | |
|   vr::VRActionHandle_t handle;
 | |
|   ld x, y;
 | |
|   function<void(ld, ld)> act;
 | |
|   analog_action_data(string s, function<void(ld, ld)> f) { action_name = s; act = f; handle = vr::k_ulInvalidActionHandle; }
 | |
|   };
 | |
| 
 | |
| struct set_data {
 | |
|   string set_name;
 | |
|   int prio;
 | |
|   vr::VRActionHandle_t handle;
 | |
|   bool_reaction_t when;
 | |
|   set_data(string s, int p, bool_reaction_t w) { set_name = s; prio = p; when = w; handle = vr::k_ulInvalidActionHandle; }
 | |
|   };
 | |
| 
 | |
| vector<digital_action_data> dads = {
 | |
|   digital_action_data("/actions/general/in/ClickLeft", [] { return true; }, [] (bool last, bool curr) {
 | |
|     move_according_to(vr::TrackedControllerRole_LeftHand, last, curr);
 | |
|     }),
 | |
|   digital_action_data("/actions/general/in/ClickRight", [] { return true; }, [] (bool last, bool curr) {
 | |
|     move_according_to(vr::TrackedControllerRole_RightHand, last, curr);
 | |
|     }),
 | |
|   digital_action_data("/actions/game/in/Drop", [] { return (cmode && sm::NORMAL); }, [] (bool last, bool curr) {
 | |
|     if(curr && !last) dialog::queue_key('g');
 | |
|     }),
 | |
|   digital_action_data("/actions/game/in/Skip turn", [] { return (cmode && sm::NORMAL); }, [] (bool last, bool curr) {
 | |
|     if(curr && !last) dialog::queue_key('s');
 | |
|     }),
 | |
|   digital_action_data("/actions/general/in/Menu", [] { return true; }, [] (bool last, bool curr) {
 | |
|     if(curr && !last) {
 | |
|       always_show_hud = !always_show_hud;
 | |
|       hmd_at_ui = hmd_at;
 | |
|       }
 | |
|     }),
 | |
|   digital_action_data("/actions/general/in/SetReference", [] { return true; }, [] (bool last, bool curr) {
 | |
|     if(curr && !last) hmd_ref_at = hmd_at;
 | |
|     })
 | |
|   };
 | |
| 
 | |
| vector<analog_action_data> aads = {
 | |
|   analog_action_data("/actions/general/in/MoveCamera", [] (ld x, ld y) {
 | |
|     vrgo_x = x;
 | |
|     vrgo_y = y;
 | |
|     }),
 | |
|   analog_action_data("/actions/general/in/RotateCamera", [] (ld x, ld y) {
 | |
|     vraim_x = x;
 | |
|     vraim_y = y;
 | |
|     }),
 | |
|   };
 | |
| 
 | |
| EX bool always_show_hud = false;
 | |
| EX bool in_actual_menu() { return (cmode & sm::VR_MENU) || !(cmode & (sm::NORMAL | sm::DRAW)); }
 | |
| EX bool in_menu() { return always_show_hud || in_actual_menu(); }
 | |
| 
 | |
| vector<set_data> sads = {
 | |
|   set_data("/actions/game", 20, [] { return cmode & sm::NORMAL; }),
 | |
|   set_data("/actions/general", 10, [] { return true; })
 | |
|   };
 | |
| 
 | |
| void init_input() {
 | |
|   const auto& vi = vr::VRInput();
 | |
| 
 | |
|   string cwd;
 | |
|   
 | |
|   char cwdbuf[PATH_MAX];
 | |
|   if (getcwd(cwdbuf, sizeof(cwdbuf)) != NULL) {
 | |
|     cwd = cwdbuf;
 | |
|     println(hlog, "Found cwd: ", cwd);
 | |
|     if(cwd.back() == '/' || cwd.back() == '\\') ;
 | |
|     else cwd += (ISWINDOWS ? '\\' : '/');
 | |
|     cwd += "hypervr_actions.json";
 | |
|     }
 | |
| 
 | |
|   vi->SetActionManifestPath(cwd.c_str());
 | |
|   
 | |
|   for(auto& sad: sads)
 | |
|     vi->GetActionSetHandle(sad.set_name.c_str(), &sad.handle);
 | |
| 
 | |
|   for(auto& dad: dads)
 | |
|     vi->GetActionHandle(dad.action_name.c_str(), &dad.handle);
 | |
| 
 | |
|   for(auto& aad: aads)
 | |
|     vi->GetActionHandle(aad.action_name.c_str(), &aad.handle);
 | |
|   }
 | |
| 
 | |
| EX void track_actions() {
 | |
| 
 | |
|   for(auto& cd: vrdata.cdata)
 | |
|     cd.clicked = false;
 | |
|   
 | |
|   vr_clicked = false;
 | |
|   
 | |
|   forward_cell = nullptr;
 | |
| 
 | |
|   vector<vr::VRActiveActionSet_t> sets;
 | |
|   
 | |
|   for(auto& sad: sads) if(sad.when()) {
 | |
|     sets.emplace_back();
 | |
|     auto& s = sets.back();
 | |
|     s.ulActionSet = sad.handle;
 | |
|     s.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle;
 | |
|     s.ulSecondaryActionSet = vr::k_ulInvalidInputValueHandle;
 | |
|     s.nPriority = sad.prio;
 | |
|     }
 | |
| 
 | |
|   if(isize(sets))
 | |
|     vr::VRInput()->UpdateActionState( &sets[0], sizeof(vr::VRActiveActionSet_t), isize(sets));
 | |
|   
 | |
|   for(auto& dad: dads) {
 | |
|     if(!dad.when()) continue;
 | |
|     vr::InputDigitalActionData_t actionData;    
 | |
|     vr::VRInput()->GetDigitalActionData(dad.handle, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle );
 | |
|     dad.last = dad.curr;
 | |
|     dad.curr = actionData.bState;
 | |
|     dad.act(dad.last, dad.curr);
 | |
|     }
 | |
|   
 | |
|   for(auto& aad: aads) {
 | |
|     vr::InputAnalogActionData_t actionData;
 | |
|     vr::VRInput()->GetAnalogActionData(aad.handle, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle );
 | |
|     aad.x = actionData.x;
 | |
|     aad.y = actionData.y;
 | |
|     aad.act(aad.x, aad.y);
 | |
|     }  
 | |
|   }
 | |
| 
 | |
| EX void get_eyes() {
 | |
|   for(int a=0; a<2; a++) {
 | |
|     auto eye = vr::EVREye(a);
 | |
|     E4;
 | |
|     vrdata.proj[a] =
 | |
|       vr_to_hr(vrdata.vr->GetProjectionMatrix(eye, 0.01, 300));
 | |
| 
 | |
|     vrdata.iproj[a] = MirrorZ * inverse(vrdata.proj[a]);
 | |
| 
 | |
|     // println(hlog, "projection = ", vrdata.proj[a]);
 | |
| 
 | |
|     vrdata.eyepos[a] =
 | |
|       vr_to_hr(vrdata.vr->GetEyeToHeadTransform(eye));
 | |
| 
 | |
|     // println(hlog, "eye-to-head = ", vrdata.eyepos[a]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void start_vr() {
 | |
| 
 | |
|   if(true) { sm = Id; sm[1][1] = sm[2][2] = -1; }
 | |
| 
 | |
|   vr::EVRInitError eError = vr::VRInitError_None;
 | |
|   vrdata.vr = vr::VR_Init( &eError, vr::VRApplication_Scene );
 | |
|   
 | |
|   for(auto& m: vrdata.last_pose_matrix) m = Id;
 | |
| 
 | |
|   if(eError != vr::VRInitError_None) {
 | |
|     error_msg = vr::VR_GetVRInitErrorAsEnglishDescription( eError );
 | |
|     println(hlog, "Unable to init VR: ", error_msg);
 | |
|     failed = true;
 | |
|     return;
 | |
|     }  
 | |
|   else println(hlog, "VR initialized successfully");
 | |
|   
 | |
|   apply_screen_settings();
 | |
| 
 | |
|   string driver = GetTrackedDeviceString( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String );
 | |
|   string display = GetTrackedDeviceString( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String );
 | |
| 
 | |
|   println(hlog, "HyperRogue VR: driver=", driver, " display=", display);
 | |
|   
 | |
|   if(!vr::VRCompositor()) {
 | |
|     println(hlog,  "Compositor initialization failed. See log file for details\n" );
 | |
|     exit(1);
 | |
|     }
 | |
|   
 | |
|   init_input();
 | |
|   
 | |
|   vrdata.vr->GetRecommendedRenderTargetSize( &vrdata.xsize, &vrdata.ysize);
 | |
|   
 | |
|   println(hlog, "recommended size: ", int(vrdata.xsize), " x ", int(vrdata.ysize));
 | |
|   
 | |
|   for(int a=0; a<2; a++) {
 | |
|     vrdata.eyes[a] = new vr_framebuffer(vrdata.xsize, vrdata.ysize);
 | |
|     println(hlog, "eye ", a, " : ", vrdata.eyes[a]->ok ? "OK" : "Error");
 | |
|     }
 | |
|     
 | |
|   get_eyes();
 | |
| 
 | |
|   //CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, leftEyeDesc );
 | |
|   //CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, rightEyeDesc );
 | |
|   
 | |
|   state = 1;
 | |
|   }
 | |
| 
 | |
| EX void shutdown_vr() {
 | |
|   vr::VR_Shutdown();
 | |
|   vrdata.vr = nullptr;
 | |
|   for(auto& e: vrdata.eyes) {
 | |
|     delete e;
 | |
|     e = nullptr;
 | |
|     }
 | |
|   for(auto& m: vrdata.models) {
 | |
|     if(m) delete m;
 | |
|     }
 | |
|   vrdata.models.clear();
 | |
|   for(auto& m: vrdata.device_models) m = nullptr;
 | |
|   state = 0;
 | |
|   }
 | |
| 
 | |
| EX void clear() {
 | |
|   if(!state) return;
 | |
|   resetbuffer rb;
 | |
|   for(int i=0; i<2; i++) {
 | |
|     auto& ey = vrdata.eyes[i];
 | |
|     glBindFramebuffer( GL_FRAMEBUFFER, ey->m_nRenderFramebufferId );
 | |
|     glViewport(0, 0, vrdata.xsize, vrdata.ysize );
 | |
|     glhr::set_depthtest(false);
 | |
|     glhr::set_depthtest(true);
 | |
|     glhr::set_depthwrite(false);
 | |
|     glhr::set_depthwrite(true);
 | |
|     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
|     }
 | |
|   rb.reset();
 | |
|   current_display->set_viewport(0);
 | |
|   }
 | |
| 
 | |
| EX ld ui_depth = 1.5;
 | |
| EX ld ui_size = 2;
 | |
| 
 | |
| #if HDR
 | |
| const ld ui_size_unit = 0.001;
 | |
| #endif
 | |
| 
 | |
| struct vr_eye_settings {
 | |
|   dynamicval<int> vx, vy;
 | |
|   dynamicval<ld> xmin, ymin, xmax, ymax;
 | |
|   
 | |
|   vr_eye_settings() :
 | |
|     vx(vid.xres, vrdata.xsize),
 | |
|     vy(vid.yres, vrdata.ysize),
 | |
|     xmin(current_display->xmin, 0),
 | |
|     ymin(current_display->ymin, 0),
 | |
|     xmax(current_display->xmax, 1),
 | |
|     ymax(current_display->ymax, 1)
 | |
|     { }    
 | |
|   
 | |
|   void use(int i) {
 | |
|     glBindFramebuffer( GL_FRAMEBUFFER, vrdata.eyes[i]->m_nRenderFramebufferId );
 | |
|     glViewport(0, 0, vrdata.xsize, vrdata.ysize );
 | |
|     calcparam();
 | |
|     }
 | |
|   
 | |
|   };
 | |
| 
 | |
| EX void in_vr_ui(reaction_t what) {
 | |
|   
 | |
|   resetbuffer rb;
 | |
|   if(!state) return;
 | |
| 
 | |
|   int ui_xmed = (ui_xmin + ui_xmax) / 2;
 | |
|   int ui_ymed = (ui_ymin + ui_ymax) / 2;
 | |
|   state = 2;
 | |
| 
 | |
|   for(int i=0; i<2; i++) {
 | |
|     E4;
 | |
|     vr_eye_settings ey;
 | |
|     ey.use(i);
 | |
|     glhr::set_depthtest(false);
 | |
|     hmd_mvp = Id;
 | |
|     hmd_mvp = xpush(-ui_xmed) * ypush(-ui_ymed) * hmd_mvp;
 | |
|     transmatrix Sca = Id;
 | |
|     Sca[0][0] *= ui_size * ui_size_unit;
 | |
|     Sca[1][1] *= -ui_size * ui_size_unit;
 | |
|     Sca[2][2] *= 0;
 | |
|     hmd_mvp = Sca * hmd_mvp;
 | |
|     hmd_mvp = zpush(-ui_depth) * hmd_mvp;
 | |
|     hmd_mvp = sm * hmd_at * inverse(hmd_at_ui) * sm * hmd_mvp;
 | |
|     hmd_mvp = vrdata.proj[i] * inverse(vrdata.eyepos[i]) * hmd_mvp;
 | |
|     reset_projection();
 | |
|     current_display->set_all(0, 0);
 | |
|     current_display->xcenter = 0;
 | |
|     current_display->ycenter = 0;
 | |
|     what();
 | |
|     }
 | |
|   state = 1;
 | |
| 
 | |
|   rb.reset();
 | |
|   calcparam();
 | |
|   current_display->set_viewport(0);
 | |
|   calcparam();
 | |
|   reset_projection();
 | |
|   current_display->set_all(0, 0);
 | |
|   glhr::set_modelview(glhr::translate(-current_display->xcenter,-current_display->ycenter, 0));
 | |
|   what();
 | |
|   }
 | |
| 
 | |
| EX void draw_eyes() {
 | |
|   state = 1;
 | |
|   for(int i=0; i<2; i++) {
 | |
|     resetbuffer rb;
 | |
|     auto& ey = vrdata.eyes[i];
 | |
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, ey->m_nRenderFramebufferId);
 | |
|     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ey->m_nResolveFramebufferId );
 | |
|     glBlitFramebuffer( 0, 0, vrdata.xsize, vrdata.ysize, 0, 0, vrdata.xsize, vrdata.ysize, GL_COLOR_BUFFER_BIT, GL_LINEAR);
 | |
|     rb.reset();
 | |
| 
 | |
|     current_display->next_shader_flags = GF_TEXTURE;
 | |
|     dynamicval<eModel> m(pmodel, mdPixel);
 | |
|     current_display->set_all(0, 0);
 | |
|     glBindTexture(GL_TEXTURE_2D, ey->m_nResolveTextureId );
 | |
|     glhr::id_modelview();
 | |
|     glhr::set_depthtest(false);
 | |
|     glhr::color2(0xFFFFFFFF);
 | |
|     vector<glhr::textured_vertex> tvx;
 | |
|     for(int a=0; a<6; a++) {
 | |
|       int dx[6] = {0, 1, 1, 0, 0, 1};
 | |
|       int dy[6] = {0, 0, 1, 0, 1, 1};
 | |
|       glhr::textured_vertex tx;
 | |
|       tx.coords[2] = 0;
 | |
|       tx.coords[3] = 1;
 | |
|       tx.coords[0] = (dx[a]+i) * current_display->xsize / 2 - current_display->xcenter;
 | |
|       tx.coords[1] = (1-dy[a]) * current_display->ysize - current_display->ycenter;
 | |
|       tx.texture[0] = dx[a];
 | |
|       tx.texture[1] = dy[a];
 | |
|       tvx.push_back(tx);
 | |
|       }
 | |
|     glhr::prepare(tvx);
 | |
|     glDrawArrays(GL_TRIANGLES, 0, 6);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void gen_mv() {
 | |
|   transmatrix mu;
 | |
|   bool pers = in_perspective();
 | |
|   ld sca = pers ? absolute_unit_in_meters : pconf.vr_scale_factor;
 | |
|   for(int i=0; i<4; i++)
 | |
|   for(int j=0; j<4; j++)
 | |
|     mu[i][j] = i!=j ? 0 : i==3 ? 1 : sca;
 | |
|   if(!pers) mu[1][1] *= pconf.stretch;
 | |
| 
 | |
|   hmd_mv = Id;
 | |
|   if(1) {
 | |
|     E4;
 | |
|     if(nisot::local_perspective_used) {
 | |
|       be_33(NLP);
 | |
|       hmd_mv = NLP * hmd_mv;          
 | |
|       }
 | |
|     hmd_mv = sm * hmd_mv;
 | |
| 
 | |
|     if(pconf.vr_angle) hmd_mv = cspin(1, 2, -pconf.vr_angle * degree) * hmd_mv;
 | |
|     if(pconf.vr_zshift) hmd_mv = euclidean_translate(0, 0, -pconf.vr_zshift) * hmd_mv;
 | |
|     hmd_mv = mu * hmd_mv;
 | |
|     if(GDIM == 2 || hsm == eHeadset::model_viewing) {
 | |
|       hmd_mv = sm * hmd_at * inverse(hmd_ref_at) * sm * hmd_mv;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX shiftmatrix master_cview;
 | |
| 
 | |
| EX void render() {
 | |
|   track_poses();
 | |
|   get_eyes();
 | |
|   resetbuffer rb;
 | |
|   state = 2;
 | |
|   vrhr::frusta.clear();
 | |
|   
 | |
|   // cscr = lshiftclick ? eCompScreen::eyes : eCompScreen::single;
 | |
| 
 | |
|   for(int i=0; i<3; i++) {
 | |
| 
 | |
|     if(1) {
 | |
|       make_actual_view();
 | |
|       master_cview = cview();
 | |
| 
 | |
|       /* unfortunately we need to backup everything that could change by shift_view... */
 | |
|       dynamicval<transmatrix> tN(NLP, NLP);
 | |
|       dynamicval<transmatrix> tV(View, View);
 | |
|       dynamicval<transmatrix> tC(current_display->which_copy, current_display->which_copy);
 | |
|       dynamicval<transmatrix> trt(current_display->radar_transform);
 | |
|       dynamicval<ld> tcs(intra::scale);
 | |
| 
 | |
|       // just in case
 | |
|       dynamicval<int> tfd(walking::floor_dir);
 | |
|       dynamicval<cell*> tof(walking::on_floor_of);
 | |
| 
 | |
|       int id = intra::current;
 | |
|       cell *co = centerover;
 | |
|       finalizer fin([&] {
 | |
|         if(intra::current != id) {
 | |
|           println(hlog, "rendering via portal");
 | |
|           intra::switch_to(id);
 | |
|           centerover = co;
 | |
|           }
 | |
|         });
 | |
| 
 | |
|       if(hsm == eHeadset::rotation_only) {
 | |
|         transmatrix T = hmd_at;
 | |
|         be_33(T);
 | |
|         apply_movement(T, smVR());
 | |
|         }
 | |
|       
 | |
|       else if(GDIM == 3 && hsm == eHeadset::reference) {
 | |
|         apply_movement(IN_E4(hmd_at * inverse(hmd_ref_at)), smVR());
 | |
|         }
 | |
| 
 | |
|       if(eyes == eEyes::truesim && i != 2) {
 | |
|         apply_movement(IN_E4(inverse(vrdata.eyepos[i])), smVR());
 | |
|         }
 | |
| 
 | |
|       make_actual_view();
 | |
|       hmd_pre = hmd_pre_for[i] = cview().T * inverse(master_cview.T);
 | |
|       current_display->radar_transform = trt.backup * inverse(hmd_pre);
 | |
|       
 | |
|       if(i < 2)
 | |
|         frusta.push_back(frustum_info{hmd_pre, NLP, false, vrdata.proj[i]});
 | |
|       else
 | |
|         frusta.push_back(frustum_info{hmd_pre, NLP, true, Id});
 | |
|       
 | |
|       if(1) {
 | |
|         gen_mv();
 | |
|         E4;
 | |
|         if(eyes == eEyes::equidistant && i != 2) {
 | |
|           eyeshift = vrdata.eyepos[i];
 | |
|           hmd_mv = inverse(eyeshift) * hmd_mv;
 | |
|           }
 | |
|         else eyeshift = Id;
 | |
|         hmd_mv_for[i] = hmd_mv;
 | |
|         if(i != 2) {
 | |
|           hmd_mvp = vrdata.proj[i] * hmd_mv;
 | |
|           eyeproj = vrdata.iproj[i];
 | |
|           }
 | |
|         }
 | |
| 
 | |
|       if(i != 2) {
 | |
|   
 | |
|         vr_eye_settings ey;
 | |
|         ey.use(i);
 | |
|         glhr::set_depthtest(false);
 | |
|         glhr::set_depthtest(true);
 | |
|         glhr::set_depthwrite(false);
 | |
|         glhr::set_depthwrite(true);
 | |
|       // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
|     
 | |
|         calcparam();
 | |
|         drawqueue();
 | |
|         }
 | |
|       
 | |
|       if(i == 2) {
 | |
|         rb.reset();
 | |
|         calcparam();
 | |
|         current_display->set_viewport(0);
 | |
|         calcparam();
 | |
|         current_display->next_shader_flags = 0;
 | |
|         current_display->set_all(0, 0);
 | |
| 
 | |
|         if(cscr == eCompScreen::single) {
 | |
|           state = 4;
 | |
|           drawqueue();
 | |
|           }
 | |
|       
 | |
|         }
 | |
|             
 | |
|       }
 | |
|     
 | |
|     }
 | |
| 
 | |
|   if(cscr == eCompScreen::reference) {
 | |
|     state = 3;
 | |
|     drawqueue();
 | |
|     }
 | |
| 
 | |
|   if(cscr == eCompScreen::eyes && !controllers_on_desktop) draw_eyes();
 | |
|   
 | |
|   render_controllers();
 | |
| 
 | |
|   if(cscr == eCompScreen::eyes && controllers_on_desktop) draw_eyes();
 | |
| 
 | |
|   state = 1;  
 | |
|   }
 | |
| 
 | |
| EX void show_vr_demos() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("VR demos"));
 | |
|   dialog::addInfo(XLAT("warning: these will restart your game!"));
 | |
|   
 | |
|   dialog::addBreak(100);
 | |
| 
 | |
|   dialog::addItem(XLAT("standard HyperRogue but in VR"), 'a');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     stop_game();
 | |
|     set_geometry(gNormal);
 | |
|     specialland = laIce;
 | |
|     set_variation(eVariation::bitruncated);
 | |
|     start_game();
 | |
|     if(GDIM == 3) invoke_embed(geom3::seNone);
 | |
|     pmodel = mdDisk;
 | |
|     pconf.alpha = 1;
 | |
|     pconf.vr_scale_factor = 1;
 | |
|     pconf.vr_angle = 0;
 | |
|     pconf.vr_zshift = 0;
 | |
|     hsm = eHeadset::model_viewing;
 | |
|     eyes = eEyes::equidistant;
 | |
|     popScreenAll();
 | |
|     });
 | |
| 
 | |
|   dialog::addItem(XLAT("HyperRogue FPP"), 'b');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     stop_game();
 | |
|     pmodel = mdDisk;
 | |
|     set_geometry(gNormal);
 | |
|     specialland = laIce;
 | |
|     set_variation(eVariation::bitruncated);
 | |
|     start_game();
 | |
|     invoke_embed(geom3::seDefault);
 | |
|     pconf.alpha = 1;
 | |
|     pconf.vr_scale_factor = 1;
 | |
|     pconf.vr_angle = 0;
 | |
|     pconf.vr_zshift = 0;
 | |
|     hsm = eHeadset::reference;
 | |
|     eyes = eEyes::equidistant;
 | |
|     popScreenAll();
 | |
|     });
 | |
| 
 | |
|   #if CAP_RUG
 | |
|   dialog::addItem(XLAT("Hypersian Rug"), 'c');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     stop_game();
 | |
|     set_geometry(gNormal);
 | |
|     specialland = laIce;
 | |
|     set_variation(eVariation::bitruncated);
 | |
|     start_game();
 | |
|     if(GDIM == 3) invoke_embed(geom3::seNone);
 | |
|     pmodel = mdDisk;
 | |
|     pconf.alpha = 1;
 | |
|     pconf.vr_scale_factor = 1;
 | |
|     pconf.vr_angle = 0;
 | |
|     pconf.vr_zshift = 0;
 | |
|     hsm = eHeadset::model_viewing;
 | |
|     rug::modelscale = 0.5;
 | |
|     eyes = eEyes::equidistant;
 | |
|     rug::init();
 | |
|     popScreenAll();
 | |
|     });
 | |
|   #endif
 | |
| 
 | |
|   dialog::addItem(XLAT("sphere from the inside"), 'd');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     stop_game();
 | |
|     set_geometry(gSphere);
 | |
|     specialland = laHalloween;
 | |
|     set_variation(eVariation::bitruncated);
 | |
|     start_game();
 | |
|     if(GDIM == 3) invoke_embed(geom3::seNone);
 | |
|     pmodel = mdDisk;
 | |
|     pconf.alpha = 0;
 | |
|     pconf.vr_scale_factor = 2;
 | |
|     pconf.vr_angle = 0;
 | |
|     pconf.vr_zshift = 0;
 | |
|     hsm = eHeadset::model_viewing;
 | |
|     eyes = eEyes::equidistant;
 | |
|     popScreenAll();
 | |
|     });
 | |
| 
 | |
|   dialog::addItem(XLAT("sphere from the outside"), 'e');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     stop_game();
 | |
|     set_geometry(gSphere);
 | |
|     specialland = laHalloween;
 | |
|     set_variation(eVariation::bitruncated);
 | |
|     start_game();
 | |
|     if(GDIM == 3) invoke_embed(geom3::seNone);
 | |
|     pmodel = mdDisk;
 | |
|     pconf.alpha = 2;
 | |
|     pconf.vr_scale_factor = 0.5;
 | |
|     pconf.vr_angle = 0;
 | |
|     pconf.vr_zshift = 0;
 | |
|     hsm = eHeadset::model_viewing;
 | |
|     eyes = eEyes::equidistant;
 | |
|     popScreenAll();
 | |
|     });
 | |
| 
 | |
|   dialog::addItem(XLAT("Thurston racing"), 'f');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     hsm = eHeadset::reference;
 | |
|     eyes = eEyes::equidistant;
 | |
|     popScreenAll();
 | |
|     pushScreen(racing::thurston_racing);
 | |
|     });
 | |
| 
 | |
|   dialog::addItem(XLAT("raytracing in H3"), 'g');
 | |
|   dialog::add_action([] {
 | |
|     if(rug::rugged) rug::close();
 | |
|     hmd_ref_at = hmd_at;
 | |
|     hsm = eHeadset::holonomy;
 | |
|     eyes = eEyes::truesim;
 | |
|     stop_game();
 | |
|     specialland = laEmerald;
 | |
|     set_geometry(gSpace534);
 | |
|     check_cgi();
 | |
|     cgi.require_basics();
 | |
|     cgi.require_shapes();
 | |
|     fieldpattern::field_from_current();
 | |
|     set_geometry(gFieldQuotient);
 | |
|     int p = 2;
 | |
|     for(;; p++) { currfp.Prime = p; currfp.force_hash = 0x72414D0C; if(!currfp.solve()) break; }
 | |
|     start_game();
 | |
|     popScreenAll();
 | |
|     });
 | |
| 
 | |
|   dialog::addBreak(100);
 | |
|   dialog::addBack();
 | |
|   
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| EX void enable_button() {
 | |
|   dialog::addBoolItem(XLAT("VR enabled"), enabled, 'o');
 | |
|   dialog::add_action([] {
 | |
|     enabled = !enabled;
 | |
|     });
 | |
|   if(!enabled)
 | |
|     dialog::addBreak(100);
 | |
|   else if(failed)
 | |
|     dialog::addInfo(XLAT("error: ") + error_msg, 0xC00000);
 | |
|   else
 | |
|     dialog::addInfo(XLAT("VR initialized correctly"), 0x00C000);
 | |
|   }
 | |
| 
 | |
| EX string refdist() {
 | |
|   E4;
 | |
|   hyperpoint h = hmd_at * inverse(hmd_ref_at) * C0;
 | |
|   return state ? fts(hypot_d(3, h)) + "m" : "";
 | |
|   }
 | |
| 
 | |
| EX void reference_button() {
 | |
|   if(enabled && among(hsm, eHeadset::reference, eHeadset::model_viewing)) {
 | |
|       
 | |
|     dialog::addSelItem(XLAT("reset the reference point"), refdist(), 'r');
 | |
|     dialog::add_action([] { hmd_ref_at = hmd_at; });
 | |
|     }
 | |
|   else dialog::addBreak(100);
 | |
|   }
 | |
| 
 | |
| EX void show_vr_settings() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("VR settings"));
 | |
| 
 | |
|   dialog::addItem(XLAT("VR demos"), 'D');
 | |
|   dialog::add_action_push(show_vr_demos);
 | |
| 
 | |
|   enable_button();
 | |
|   
 | |
|   dialog::addBreak(100);
 | |
| 
 | |
|   add_edit(hsm);   
 | |
|   if(enabled && GDIM == 2 && among(hsm, eHeadset::holonomy, eHeadset::reference))
 | |
|     dialog::addInfo(XLAT("(this setting is for 3D geometries only, use 'model viewing' instead)"));
 | |
|   add_edit(eyes);
 | |
|   add_edit(cscr);
 | |
|   
 | |
|   dialog::addSelItem(XLAT("absolute unit in meters"), fts(absolute_unit_in_meters), 'a');
 | |
|   dialog::add_action([] {
 | |
|     dialog::editNumber(absolute_unit_in_meters, .01, 100, 0.1, 1, XLAT("absolute unit in meters"), 
 | |
|       XLAT(
 | |
|         "The size of the absolute unit of the non-Euclidean geometry in meters. "
 | |
|         "This affects the headset movement and binocular vision.\n\n"
 | |
|         "In spherical geometry, the absolute unit is the radius of the sphere. "
 | |
|         "The smaller the absolute unit, the stronger the non-Euclidean effects.\n\n"
 | |
|         "Elements of the HyperRogue world have fixed size in terms of absolute units, "
 | |
|         "so reducing the absolute unit makes them smaller. "
 | |
|         "If you are playing in the Euclidean mode, this feature just scales everything "
 | |
|         "(e.g., in the cube tiling, the 'absolute unit' is just the edge of the cube). "
 | |
|         "Only perspective projections are affected, other models use the 'VR scale' setting "
 | |
|         "from the Projections menu."
 | |
|         ));
 | |
|       dialog::scaleLog();
 | |
|       });
 | |
| 
 | |
|   dialog::addSelItem(XLAT("projection"), current_proj_name(), 'M');
 | |
|   dialog::add_action_push(models::model_menu);
 | |
|     
 | |
|   reference_button();
 | |
| 
 | |
|   dialog::addSelItem(XLAT("pointer length"), fts(pointer_length) + "m", 'p');
 | |
|   dialog::add_action([] {
 | |
|     dialog::editNumber(pointer_length, 0, 2, 0.1, 1, XLAT("pointer length"), 
 | |
|       XLAT(
 | |
|         "If the pointer length is 0.5m, the object pointed to is 0.5 meter from the controller. "
 | |
|         "This is used in situations where the controller is used as a 3D mouse, e.g., "
 | |
|         "the drawing tool in three-dimensional geometries. When pointing at two-dimensional surfaces, "
 | |
|         "this is not relevant (the pointer is as long as needed to hit the surface)."
 | |
|         ));
 | |
|       });
 | |
| 
 | |
|   dialog::addSelItem(XLAT("UI size"), fts(ui_size) + "mm", 'u');
 | |
|   dialog::add_action([] {
 | |
|     dialog::editNumber(ui_size, 0, 10, 0.1, 2, XLAT("UI size"), 
 | |
|       XLAT(
 | |
|         "How big is a pixel of the user interface (HUD and menus). The user interface is as big as the window on the desktop."
 | |
|         ));
 | |
|       });
 | |
|     
 | |
|   dialog::addSelItem(XLAT("UI depth"), fts(ui_depth) + "m", 'U');
 | |
|   dialog::add_action([] {
 | |
|     dialog::editNumber(ui_depth, 0, 2, 0.1, 1, XLAT("UI depth"), 
 | |
|       XLAT(
 | |
|         "How far to show the user interface (HUD and menus)."
 | |
|         ));
 | |
|       });    
 | |
|   
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| #if CAP_COMMANDLINE
 | |
| int readArgs() {
 | |
|   using namespace arg;
 | |
|            
 | |
|   if(0) ;
 | |
|   else if(argis("-vr-enabled")) {
 | |
|     PHASEFROM(2);
 | |
|     shift(); enabled = argi();
 | |
|     }
 | |
|   else if(argis("-vr-absunit")) {
 | |
|     PHASEFROM(2);
 | |
|     shift_arg_formula(absolute_unit_in_meters);
 | |
|     }
 | |
|   else if(argis("-vr-scale")) {
 | |
|     PHASEFROM(2);
 | |
|     shift_arg_formula(pconf.vr_scale_factor);
 | |
|     }
 | |
|   else if(argis("-vr-z")) {
 | |
|     PHASEFROM(2);
 | |
|     shift_arg_formula(pconf.vr_zshift);
 | |
|     }
 | |
|   else if(argis("-vr-pl")) {
 | |
|     PHASEFROM(2);
 | |
|     shift_arg_formula(pointer_length);
 | |
|     }
 | |
|   else if(argis("-vr-angle")) {
 | |
|     PHASEFROM(2);
 | |
|     shift_arg_formula(pconf.vr_angle);
 | |
|     }
 | |
|   else if(argis("-d:vr")) {
 | |
|     PHASEFROM(2); launch_dialog(show_vr_settings);
 | |
|     }
 | |
|   else if(argis("-d:vrd")) {
 | |
|     PHASEFROM(2); launch_dialog(show_vr_demos);
 | |
|     }
 | |
|   else if(argis("-vr-mode")) {
 | |
|     PHASEFROM(2); 
 | |
|     shift(); hsm = (eHeadset) argi();
 | |
|     shift(); eyes = (eEyes) argi();
 | |
|     shift(); cscr = (eCompScreen) argi();
 | |
|     }
 | |
|   else return 1;
 | |
|   return 0;
 | |
|   }
 | |
| auto hooka = addHook(hooks_args, 100, readArgs);
 | |
| #endif
 | |
| 
 | |
| #if CAP_CONFIG
 | |
| void addconfig() {
 | |
|   param_b(enabled, "vr-enabled");
 | |
| 
 | |
|   param_f(absolute_unit_in_meters, "vr-abs-unit");
 | |
| 
 | |
|   param_f(pconf.vr_scale_factor, "vr_scale");
 | |
|   param_f(pconf.vr_zshift, "vr_zshift");
 | |
|   param_f(pconf.vr_angle, "vr_angle");
 | |
| 
 | |
|   auto& rrconf = vid.rug_config;
 | |
|   param_f(rrconf.vr_scale_factor, "rug_vr_scale");
 | |
|   param_f(rrconf.vr_zshift, "rug_vr_shift");
 | |
|   param_f(rrconf.vr_angle, "rug_vr_angle");
 | |
| 
 | |
|   param_f(vrhr::pointer_length, "vr_pointer_length");
 | |
|   param_f(vrhr::ui_depth, "vr_ui_depth");
 | |
|   param_f(vrhr::ui_size, "vr_ui_size");
 | |
|   
 | |
|   param_enum(vrhr::hsm, "vr_headset_mode", vrhr::hsm)
 | |
|     ->editable(headset_desc, "VR headset movement", 'h');
 | |
|   param_enum(vrhr::eyes, "vr_eyes_mode", vrhr::eyes)
 | |
|     ->editable(eyes_desc, "VR binocular vision", 'b');
 | |
|   param_enum(vrhr::cscr, "vr_screen_mode", vrhr::cscr)
 | |
|     ->editable(comp_desc, "VR computer screen", 'c');
 | |
|   }
 | |
| auto hookc = addHook(hooks_configfile, 100, addconfig);
 | |
| #endif
 | |
| 
 | |
| EX bool rec;
 | |
| 
 | |
| EX void render_controllers() {
 | |
|   if(!state) return;
 | |
|   dynamicval<bool> rc(rec, true);
 | |
|   for(int i=0; i<(int)vr::k_unMaxTrackedDeviceCount; i++) 
 | |
|     if(vrdata.device_models[i]) {
 | |
|       resetbuffer rb;
 | |
|     
 | |
|       state = 2;
 | |
|       dynamicval<eModel> m(pmodel, mdPerspective);
 | |
|       dynamicval<ld> ms(sightranges[geometry], 100);
 | |
|       
 | |
|       for(int e=0; e<3; e++) {
 | |
|       
 | |
|         if(e < 2) {
 | |
|           vr_eye_settings ey;
 | |
|           ey.use(e);
 | |
|           }
 | |
|         
 | |
|         else {
 | |
|           state = 1;
 | |
|           
 | |
|           rb.reset();
 | |
|           calcparam();
 | |
|           current_display->set_viewport(0);
 | |
|           calcparam();
 | |
|           reset_projection();
 | |
|           current_display->set_all(0, 0);
 | |
| 
 | |
|           if(cscr != eCompScreen::single || !controllers_on_desktop) goto next_model;
 | |
|           state = 4;
 | |
|           }
 | |
| 
 | |
|         E4;
 | |
| 
 | |
|         hmd_mvp = sm * hmd_at * vrdata.pose_matrix[i] * sm * Id;
 | |
|         if(e < 2) 
 | |
|           hmd_mvp = vrdata.proj[e] * inverse(vrdata.eyepos[e]) * hmd_mvp;
 | |
|         else
 | |
|           hmd_mv = hmd_mvp;
 | |
|         hmd_pre = Id;
 | |
|         
 | |
|         reset_projection();        
 | |
|         current_display->next_shader_flags = GF_TEXTURE;
 | |
|         current_display->set_all(0, 0);
 | |
|         glhr::set_depthtest(false);
 | |
|         glhr::set_depthtest(true);
 | |
|         glhr::set_depthwrite(false);
 | |
|         glhr::set_depthwrite(true);
 | |
|         glClear(GL_DEPTH_BUFFER_BIT);
 | |
|         glhr::id_modelview();
 | |
|         glhr::color2(0xFFFFFFFF);
 | |
|         prepare(vrdata.device_models[i]->vertices);
 | |
|         
 | |
|         glBindTexture(GL_TEXTURE_2D, vrdata.device_models[i]->texture_id);
 | |
|         glDrawArrays(GL_TRIANGLES, 0, isize(vrdata.device_models[i]->vertices));
 | |
| 
 | |
|         if(i == which_pointer) {
 | |
|           current_display->next_shader_flags = 0;
 | |
|           current_display->set_all(0, 0);
 | |
|           vector<glvertex> vex;
 | |
|           vex.push_back(glhr::makevertex(0.01, 0, 0));
 | |
|           vex.push_back(glhr::makevertex(-0.01, 0, 0));
 | |
|           vex.push_back(glhr::makevertex(0, 0, -pointer_distance));
 | |
|           glhr::current_vertices = nullptr;
 | |
|           glhr::vertices(vex);
 | |
|           glhr::color2(0xC0FFC0C0);
 | |
|           glDrawArrays(GL_TRIANGLES, 0, 3);
 | |
|           }
 | |
| 
 | |
|         }
 | |
|       
 | |
|       next_model: ;
 | |
|       }
 | |
|   }
 | |
| 
 | |
| EX void submit() {
 | |
| 
 | |
|   if(!state) return;
 | |
|       
 | |
|   for(int i=0; i<2; i++) {
 | |
|     auto eye = vr::EVREye(i);
 | |
|     auto& ey = vrdata.eyes[i];
 | |
|         
 | |
|     resetbuffer rb;
 | |
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, ey->m_nRenderFramebufferId);
 | |
|     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ey->m_nResolveFramebufferId );
 | |
|     glBlitFramebuffer( 0, 0, vrdata.xsize, vrdata.ysize, 0, 0, vrdata.xsize, vrdata.ysize, GL_COLOR_BUFFER_BIT, GL_LINEAR);
 | |
|     rb.reset();
 | |
| 
 | |
|     vr::Texture_t texture = {(void*)(uintptr_t)ey->m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
 | |
|     vr::VRCompositor()->Submit(eye, &texture );
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void handoff() {
 | |
|   if(!state) return;
 | |
|   vr::VRCompositor()->PostPresentHandoff();  
 | |
|   }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| EX purehookset vr_quickmenu_extensions;
 | |
| 
 | |
| EX void show_vr_quickmenu() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   dialog::init(XLAT("VR quickmenu"));
 | |
|   dialog::addHelp(XLAT("These hotkeys can be activated at any time by pressing Alt+key. They are mostly useful when showing VR to someone. Demos can define extra hotkeys."));
 | |
|   callhooks(vr_quickmenu_extensions);
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| bool vr_keys(int sym, int uni) {
 | |
|   #if !ISMOBILE
 | |
|   if(!(cmode & sm::NORMAL)) return false;
 | |
|   const sdl_keystate_type *keystate = SDL12_GetKeyState(NULL);
 | |
|   if(keystate[SDL12(SDLK_LALT, SDL_SCANCODE_LALT)] || keystate[SDL12(SDLK_RALT, SDL_SCANCODE_RALT)])
 | |
|     {
 | |
|     dialog::key_actions.clear();
 | |
|     callhooks(vr_quickmenu_extensions);
 | |
|     if(dialog::key_actions.count(uni)) { dialog::key_actions[uni](); return true; }
 | |
|     }
 | |
|   return false;
 | |
|   #endif
 | |
|   }
 | |
| 
 | |
| auto hookvr = addHook(vr_quickmenu_extensions, 100, [] {
 | |
|   dialog::addSelItem(XLAT("increase camera speed"), fts(camera_speed), ',');
 | |
|   dialog::add_action([] { camera_speed *= 1.2; println(hlog, "camera_speed = ", camera_speed); });
 | |
|   dialog::addSelItem(XLAT("decrease camera speed"), fts(camera_speed), '.');
 | |
|   dialog::add_action([] { camera_speed /= 1.2; println(hlog, "camera_speed = ", camera_speed); });
 | |
|   #if CAP_VR
 | |
|   if(vrhr::active()) {
 | |
|     if(in_perspective()) {
 | |
|       dialog::addSelItem(XLAT("increase absolute unit"), fts(vrhr::absolute_unit_in_meters), 'a');
 | |
|       dialog::add_action([] {
 | |
|         vrhr::absolute_unit_in_meters *= 1.2;
 | |
|         walking::eye_level *= 1.2;
 | |
|         println(hlog, "vr absolute unit set to ", vrhr::absolute_unit_in_meters);
 | |
|         });
 | |
|       dialog::addSelItem(XLAT("decrease absolute unit"), fts(vrhr::absolute_unit_in_meters), 'z');
 | |
|       dialog::add_action([] {
 | |
|         vrhr::absolute_unit_in_meters /= 1.2;
 | |
|         walking::eye_level /= 1.2;
 | |
|         println(hlog, "vr absolute unit set to ", vrhr::absolute_unit_in_meters);
 | |
|         });
 | |
|       }
 | |
|     else {
 | |
|       dialog::addSelItem(XLAT("increase model size"), fts(pconf.vr_scale_factor), 'a');
 | |
|       dialog::add_action([] {
 | |
|         pconf.vr_scale_factor *= 1.2;
 | |
|         println(hlog, "vr scale factor set to ", pconf.vr_scale_factor);
 | |
|         });
 | |
|       dialog::addSelItem(XLAT("decrease model size"), fts(pconf.vr_scale_factor), 'z');
 | |
|       dialog::add_action([] {
 | |
|         pconf.vr_scale_factor *= 1.2;
 | |
|         println(hlog, "vr scale factor set to ", pconf.vr_scale_factor);
 | |
|         });
 | |
|       dialog::addSelItem(XLAT("increase Z-shift"), fts(pconf.vr_zshift), 'c');
 | |
|       dialog::add_action([] {
 | |
|         pconf.vr_zshift += 0.5;
 | |
|         });
 | |
|       dialog::addSelItem(XLAT("decrease Z-shift"), fts(pconf.vr_zshift), 'd');
 | |
|       dialog::add_action([] {
 | |
|         pconf.vr_zshift -= 0.5;
 | |
|         });
 | |
|       }
 | |
|     dialog::addBoolItem(XLAT("always show HUD"), always_show_hud, 'x');
 | |
|     dialog::add_action([] {
 | |
|       always_show_hud = !always_show_hud;
 | |
|       println(hlog, "hud ", ONOFF(always_show_hud));
 | |
|       });
 | |
|     dialog::addSelItem(XLAT("reset VR reference"), refdist(), 'v');
 | |
|     dialog::add_action([] {
 | |
|       println(hlog, "vr reference reset");
 | |
|       vrhr::hmd_ref_at = vrhr::hmd_at;
 | |
|       });
 | |
|     }
 | |
|   #endif
 | |
|   dialog::addItem(XLAT("VR quickmenu help"), 'h');
 | |
|   dialog::add_action_push(show_vr_quickmenu);
 | |
|   })
 | |
|   + addHook(hooks_handleKey, 101, vr_keys);
 | |
| 
 | |
| EX }
 | |
| }
 | 
