initial implementation of VR

This commit is contained in:
Zeno Rogue 2020-11-19 18:20:06 +01:00
parent 4444fa6bf1
commit 0de8ce9a10
16 changed files with 1315 additions and 49 deletions

View File

@ -293,6 +293,10 @@ EX void setGLProjection(color_t col IS(backcolor)) {
glClearColor(part(col, 2) / 255.0, part(col, 1) / 255.0, part(col, 0) / 255.0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
#if CAP_VR
vrhr::clear();
#endif
GLERR("setGLProjection #1");

View File

@ -1911,6 +1911,12 @@ EX void show3D() {
dialog::addInfo(XLAT("parameters set correctly"));
dialog::addBreak(50);
dialog::addItem(XLAT("stereo vision config"), 'e');
#if CAP_VR
dialog::addBoolItem(XLAT("VR settings"), vrhr::state > 0, 'v');
dialog::add_action_push(vrhr::show_vr_settings);
#endif
dialog::addBack();
dialog::display();

View File

@ -26,6 +26,7 @@ EX bool holdmouse;
EX int getcstat, lgetcstat;
EX ld getcshift;
EX bool inslider;
EX int slider_x;
EX function <void(int sym, int uni)> keyhandler = [] (int sym, int uni) {};
EX function <bool(SDL_Event &ev)> joyhandler = [] (SDL_Event &ev) {return false;};
@ -134,6 +135,11 @@ EX void movepckeydir(int d) {
if(!canmove) movepcto(md), remission(); else movepcto(md);
}
EX void movevrdir(hyperpoint vec) {
movedir md = vectodir(vec);
if(!canmove) movepcto(md), remission(); else movepcto(md);
}
EX void calcMousedest() {
if(mouseout()) return;
if(vid.revcontrol == true) { mouseh[0] = -mouseh[0]; mouseh[1] = -mouseh[1]; }
@ -278,6 +284,7 @@ EX bool quitmainloop = false;
EX bool doexiton(int sym, int uni) {
if(sym == SDLK_ESCAPE) return true;
if(sym == SDLK_F10) return true;
if(sym == PSEUDOKEY_EXIT) return true;
if(sym == PSEUDOKEY_RELEASE) return false;
#ifndef FAKE_SDL
if(sym == SDLK_LSHIFT) return false;
@ -329,6 +336,14 @@ EX void full_forward_camera(ld t) {
}
}
EX void full_strafe_camera(ld t) {
if(GDIM == 3) {
shift_view(ctangent(0, t * camera_speed));
didsomething = true;
playermoved = false;
}
}
EX void full_rotate_camera(int dir, ld val) {
if(rug::rug_control() && lshiftclick) {
val *= camera_rot_speed;
@ -548,6 +563,9 @@ EX void handleKeyNormal(int sym, int uni) {
if(sym == 'v' && DEFAULTNOR(sym))
pushScreen(showMainMenu);
if(sym == PSEUDOKEY_MENU)
pushScreen(showMainMenu);
if(sym == '-' || sym == PSEUDOKEY_WHEELDOWN) {
actonrelease = false;
@ -643,6 +661,10 @@ EX void mainloopiter() {
vid.monmode = 0;
#endif
#if CAP_VR
vrhr::vr_shift();
#endif
optimizeview();
models::configure();
@ -759,6 +781,18 @@ EX void mainloopiter() {
SDL_Event ev;
DEBB(DF_GRAPH, ("polling for events\n"));
#if CAP_VR
if(vrhr::state) {
rug::using_rugview urv;
dynamicval<bool> ds(didsomething, didsomething);
using namespace vrhr;
if(vraim_x) full_rotate_camera(0, -vraim_x / 20);
if(vraim_y) full_rotate_camera(1, vraim_y / 20);
if(vrgo_y) full_forward_camera(-vrgo_y / 20);
if(vrgo_x) full_strafe_camera(-vrgo_x / 20);
}
#endif
if(mouseaiming(shmup::on)) {
#if CAP_MOUSEGRAB
rug::using_rugview urv;
@ -787,7 +821,17 @@ EX void mainloopiter() {
}
else sc_ticks = ticks;
#if CAP_VR
vrhr::vr_control();
#endif
achievement_pump();
for(auto d: dialog::key_queue) {
println(hlog, "handling key ", d);
handlekey(d, d);
}
dialog::key_queue.clear();
while(SDL_PollEvent(&ev)) handle_event(ev);
fix_mouseh();
#if CAP_SDLJOY

View File

@ -379,7 +379,17 @@ EX namespace dialog {
EX purehookset hooks_display_dialog;
EX vector<int> key_queue;
EX void queue_key(int key) { key_queue.push_back(key); }
EX void display() {
#if CAP_VR
for(auto h: vrhr::get_hits())
displaystr(h.x, h.y, 2, vid.fsize * 2, "X", 0xFFD500, 8);
#endif
callhooks(hooks_display_dialog);
int N = items.size();
dfsize = vid.fsize;
@ -432,6 +442,16 @@ EX namespace dialog {
xthis = xthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8);
if(xthis) getcstat = I.key;
#if CAP_VR
for(auto h: vrhr::get_hits()) {
bool vrthis = (h.y >= top && h.y < tothei);
if(cmode & sm::DIALOG_STRICT_X)
vrthis = vrthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
if(vrthis) I.color = I.colors;
if(vrthis && h.clicked) queue_key(I.key);
}
#endif
}
else if(I.type == diItem || I.type == diBigItem) {
bool xthis = (mousey >= top && mousey < tothei);
@ -449,6 +469,19 @@ EX namespace dialog {
I.color = (xthis&&mousepressed&&actonrelease) ? I.colorc : I.colors;
}
#endif
#if CAP_VR
for(auto h: vrhr::get_hits()) {
bool vrthis = (h.y >= top && h.y < tothei);
if(cmode & sm::DIALOG_STRICT_X)
vrthis = vrthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
if(vrthis) I.color = I.colors;
if(vrthis && h.clicked) {
queue_key(I.key);
}
}
#endif
if(I.type == diBigItem) {
displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8);
}
@ -482,7 +515,17 @@ EX namespace dialog {
displayfr(sl + double(sw * I.p1 / I.p2), mid, 2, dfsize * I.scale/100, "#", I.color, 8);
displayfr(sr, mid, 2, dfsize * I.scale/100, "}", I.color, 0);
}
if(xthis) getcstat = I.key, inslider = true;
if(xthis) getcstat = I.key, inslider = true, slider_x = mousex;
#if CAP_VR
for(auto h: vrhr::get_hits()) {
bool vrthis = (h.y >= top && h.y < tothei);
if(cmode & sm::DIALOG_STRICT_X)
vrthis = vrthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
if(vrthis) I.color = I.colors;
if(vrthis && h.clicked) queue_key(I.key), inslider = true, slider_x = h.x;
}
#endif
}
else if(I.type == diKeyboard) {
int len = 0;
@ -612,7 +655,7 @@ EX namespace dialog {
int shift = colorAlpha ? 0 : 8;
if(uni >= 'A' && uni <= 'D') {
int x = (mousex - (dcenter-dwidth/4)) * 510 / dwidth;
int x = (slider_x - (dcenter-dwidth/4)) * 510 / dwidth;
if(x < 0) x = 0;
if(x > 255) x = 255;
part(color, uni - 'A') = x;
@ -705,7 +748,14 @@ EX namespace dialog {
displayColorButton(dcenter - dwidth/4 + dwidth * part(color, i) / 510, y, "#", 0, 8, 0, col);
if(mousey >= y - vid.fsize && mousey < y + vid.fsize)
getcstat = 'A' + i, inslider = true;
getcstat = 'A' + i, inslider = true, slider_x = mousex;
#if CAP_VR
for(auto h: vrhr::get_hits()) {
bool vrthis = (h.y >= y - vid.fsize && h.y < y + vid.fsize);
if(vrthis && h.clicked) queue_key('A' + i), inslider = true, slider_x = h.x;
}
#endif
}
displayColorButton(dcenter, vid.yres/2+vid.fsize * 6, XLAT("select this color") + " : " + format(colorAlpha ? "%08X" : "%06X", color), ' ', 8, 0, color >> (colorAlpha ? ash : 0));
@ -884,7 +934,7 @@ EX namespace dialog {
sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2;
else
sl = vid.xres/4, sr = vid.xres*3/4;
ld d = (mousex - sl + .0) / (sr-sl);
ld d = (slider_x - sl + .0) / (sr-sl);
ld val = ne.sc.inverse(d * (ne.sc.direct(ne.vmax) - ne.sc.direct(ne.vmin)) + ne.sc.direct(ne.vmin));
ld nextval = ne.sc.inverse((mousex + 1. - sl) / (sr - sl) * (ne.sc.direct(ne.vmax) - ne.sc.direct(ne.vmin)) + ne.sc.direct(ne.vmin));
ld dif = abs(val - nextval);

View File

@ -208,22 +208,31 @@ EX void glflush() {
}
shapes_merged = 0;
#endif
if(isize(text_vertices)) {
// printf("%08X | %d texts, %d vertices\n", text_color, texts_merged, isize(text_vertices));
current_display->next_shader_flags = GF_TEXTURE;
dynamicval<eModel> m(pmodel, mdPixel);
if(!svg::in) current_display->set_all(0,0);
glBindTexture(GL_TEXTURE_2D, text_texture);
glhr::color2(text_color);
glhr::set_depthtest(false);
for(int ed = (current_display->stereo_active() && text_shift)?-1:0; ed<2; ed+=2) {
glhr::set_modelview(glhr::translate(-ed*text_shift-current_display->xcenter,-current_display->ycenter, 0));
current_display->set_mask(ed);
auto drawer = [] {
glhr::color2(text_color);
glBindTexture(GL_TEXTURE_2D, text_texture);
glhr::set_depthtest(false);
glhr::current_vertices = NULL;
glhr::prepare(text_vertices);
glDrawArrays(GL_TRIANGLES, 0, isize(text_vertices));
};
#if CAP_VR
if(vrhr::state == 1 && !(cmode & sm::NORMAL))
vrhr::in_vr_ui(drawer);
else
#endif
for(int ed = (current_display->stereo_active() && text_shift)?-1:0; ed<2; ed+=2) {
glhr::set_modelview(glhr::translate(-ed*text_shift-current_display->xcenter,-current_display->ycenter, 0));
current_display->set_mask(ed);
drawer();
GLERR("print");
}
@ -2294,11 +2303,18 @@ EX void draw_main() {
EX void drawqueue() {
DEBBI(DF_GRAPH, ("drawqueue"));
#if CAP_WRL
if(wrl::in) { wrl::render(); return; }
#endif
#if CAP_VR
if(vrhr::state == 1) {
vrhr::render();
return;
}
#endif
callhooks(hooks_drawqueue);
current_display->next_shader_flags = 0;
reset_projection();

View File

@ -1235,6 +1235,9 @@ void geometry_information::make_floor_textures_here() {
cd->radius = cd->scrsize * pconf.scale;
floor_textures->enable();
#if CAP_VR
dynamicval<int> i(vrhr::state, 0);
#endif
floor_textures->clear(0); // 0xE8E8E8 = 1
// gradient vertices

View File

@ -3724,6 +3724,10 @@ void make_clipping_planes() {
#if MAXMDIM >= 4
clipping_planes.clear();
if(!frustum_culling || PIU(sphere) || experimental || vid.stereo_mode == sODS || panini_alpha) return;
#if CAP_VR
if(vrhr::state) return;
#endif
auto add_clipping_plane = [] (ld x1, ld y1, ld x2, ld y2) {
ld z1 = 1, z2 = 1;
hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1);
@ -4151,6 +4155,11 @@ EX void queuecircleat(cell *c, double rad, color_t col) {
#endif
EX cell *forwardcell() {
#if CAP_VR
if(vrhr::state) {
return vrhr::forward_cell;
}
#endif
movedir md = vectodir(move_destination_vec(6));
cellwalker xc = cwt + md.d + wstep;
return xc.at;
@ -4279,7 +4288,7 @@ EX void drawMarkers() {
if(GDIM == 3 && !inHighQual && !shmup::on && vid.axes3 && playermoved) {
cell *c = forwardcell();
queuecircleat(c, .8, getcs().uicolor);
if(c) queuecircleat(c, .8, getcs().uicolor);
}
#endif
@ -4868,10 +4877,14 @@ EX void calcparam() {
cd->ycenter = lerp(vid.fsize + cd->scrsize, vid.yres - cd->scrsize - vid.fsize, .8);
}
else {
if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE))
bool ok = true;
#if CAP_VR
ok = ok && !vrhr::state;
#endif
if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE) && ok)
current_display->sidescreen = true;
#if CAP_TOUR
if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN))
if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN) && ok)
current_display->sidescreen = true;
#endif
@ -5017,6 +5030,9 @@ EX void gamescreen(int _darken) {
}
darken = _darken;
#if CAP_VR
if(vrhr::state) darken = 0;
#endif
if(history::includeHistory) history::restore();
@ -5062,6 +5078,31 @@ EX void gamescreen(int _darken) {
if(texture::config.tstate == texture::tsAdjusting)
texture::config.drawRawTexture();
#endif
#if CAP_VR
if(vrhr::state && _darken) {
int xsi = current_display->xsize;
int ysi = current_display->ysize;
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(0, 0, 0));
vs.emplace_back(glhr::makevertex(xsi, 0, 0));
vs.emplace_back(glhr::makevertex(xsi, ysi, 0));
vs.emplace_back(glhr::makevertex(0, 0, 0));
vs.emplace_back(glhr::makevertex(0, ysi, 0));
vs.emplace_back(glhr::makevertex(xsi, ysi, 0));
glhr::current_vertices = NULL;
glhr::vertices(vs);
glDrawArrays(GL_TRIANGLES, 0, 6);
});
}
#endif
}
EX bool nohelp;
@ -5232,6 +5273,11 @@ EX void drawscreen() {
glflush();
DEBB(DF_GRAPH, ("swapbuffers"));
#if CAP_VR
vrhr::submit();
#endif
#if CAP_SDL
#if CAP_GL
if(vid.usingGL) SDL_GL_SwapBuffers(); else

View File

@ -120,6 +120,7 @@
#include "multigame.cpp"
#include "inforder.cpp"
#include "dpgen.cpp"
#include "vr.cpp"
#if CAP_ROGUEVIZ
#include "rogueviz/rogueviz-all.cpp"

View File

@ -1592,6 +1592,38 @@ EX hyperpoint vertical_vector() {
}
EX void spinEdge(ld aspd) {
#if CAP_VR
if(vrhr::state && keep_vertical()) {
transmatrix T = vrhr::hmd_ref_at;
T = vrhr::sm * inverse(T);
vrhr::be_33(T);
transmatrix V = T * get_view_orientation();
hyperpoint h = inverse(V) * C0;
V = V * rgpushxto0(h);
V = cspin(2, 1, 90 * degree) * V;
if(1) {
dynamicval<eGeometry> g(geometry, gSphere);
bool b = vid.always3;
vid.always3 = false;
geom3::apply_always3();
V = gpushxto0(V*C0) * V;
if(b) {
vid.always3 = b;
geom3::apply_always3();
}
}
V = cspin(1, 2, 90 * degree) * V;
get_view_orientation() = inverse(T) * V * gpushxto0(h);
return;
}
#endif
ld downspin = 0;
auto& ds = downseek;
if(dual::state == 2 && (dual::one_euclidean ? !euclid : dual::currently_loaded != dual::main_side)) {

View File

@ -598,7 +598,7 @@ EX namespace sn {
"if(iz < 0.05 && ix > .85 && iy > .45 && iy < .75) ok = false;"
"if(iz < 0.025 && ix > .65 && iy > .65 && ix < .8 && iy < .8) ok = false;"
"if(!ok) res = vec4(0,0,0,1);"
"if(!ok) res = vec4(0./0.,0./0.,0./0.,1);"
"else "
"\n#endif\n"

View File

@ -76,6 +76,9 @@ bool need_many_cell_types() {
/** is the raycaster available? */
EX bool available() {
#if CAP_VR
if(vrhr::state) return false; /* not implemented */
#endif
if(noGUI) return false;
if(!vid.usingGL) return false;
if(GDIM == 2) return false;

View File

@ -1021,6 +1021,9 @@ EX void prepareTexture() {
dynamicval<eStereo> d(vid.stereo_mode, sOFF);
dynamicval<ld> dl(levellines, 0);
#if CAP_VR
dynamicval<int> i(vrhr::state, 0);
#endif
calcparam_rug();
models::configure();
@ -1084,6 +1087,9 @@ EX void drawRugScene() {
auto& rug = queuecurve(shiftless(Id), 0, 0xFFFFFFFF, PPR::LINE);
dynamicval<transmatrix> tV(View, View);
View = Id; /* needed for vr */
if(nonisotropic) {
transmatrix T2 = eupush( tC0(view_inverse(rugView)) );
NLP = rugView * T2;

View File

@ -202,7 +202,13 @@ shared_ptr<glhr::GLprogram> write_shader(flagtype shader_flags) {
"mediump float d = dot(t.xyz, t.xyz);\n"
"mediump float hz = (1.+d) / (1.-d);\n"
"mediump float ad = acosh(hz);\n"
"mediump float m = d == 0. ? 0. : d >= 1. ? 1.e4 : (hz+1.) * ad / sinh(ad);\n"
"mediump float m = d == 0. ? 0. : d >= 1. ? 1.e4 : (hz+1.) * ad / sinh(ad);\n";
#if CAP_VR
if(vrhr::state == 2)
coordinator += "t.xyz *= ad/d;\n";
else
#endif
coordinator +=
"t.xyz *= m;\n";
distfun = "ad";
}
@ -295,7 +301,11 @@ shared_ptr<glhr::GLprogram> write_shader(flagtype shader_flags) {
if(!skip_t) {
vmain += "mediump vec4 t = uMV * aPosition;\n";
vmain += coordinator;
if(GDIM == 3 && WDIM == 2 && hyperbolic && context_fog && pmodel == mdPerspective) {
bool ok = true;
#if CAP_VR
if(vrhr::state) ok = false;
#endif
if(GDIM == 3 && WDIM == 2 && hyperbolic && context_fog && ok && pmodel == mdPerspective) {
vsh +=
"uniform mediump mat4 uRadarTransform;\n"
"uniform mediump sampler2D tAirMap;\n"
@ -407,6 +417,9 @@ void display_data::set_projection(int ed, ld shift) {
id <<= 6; id |= shader_flags;
id <<= 6; id |= spherephase;
id <<= 1; if(vid.consider_shader_projection) id |= 1;
#if CAP_VR
id <<= 3; id |= vrhr::state;
#endif
id <<= 2; id |= (spherespecial & 3);
if(sol && solv_all) id |= 1;
if(in_h2xe()) id |= 1;
@ -459,22 +472,28 @@ void display_data::set_projection(int ed, ld shift) {
}
glhr::new_projection();
if(ed && vid.stereo_mode == sLR) {
glhr::projection_multiply(glhr::translate(ed, 0, 0));
glhr::projection_multiply(glhr::scale(2, 1, 1));
}
ld tx = (cd->xcenter-cd->xtop)*2./cd->xsize - 1;
ld ty = (cd->ycenter-cd->ytop)*2./cd->ysize - 1;
glhr::projection_multiply(glhr::translate(tx, -ty, 0));
if(pmodel == mdManual) return;
if(pconf.stretch != 1 && (shader_flags & SF_DIRECT) && pmodel != mdPixel) glhr::projection_multiply(glhr::scale(1, pconf.stretch, 1));
#if CAP_VR
if(vrhr::state != 2) {
#else
if(true) {
#endif
if(ed && vid.stereo_mode == sLR) {
glhr::projection_multiply(glhr::translate(ed, 0, 0));
glhr::projection_multiply(glhr::scale(2, 1, 1));
}
ld tx = (cd->xcenter-cd->xtop)*2./cd->xsize - 1;
ld ty = (cd->ycenter-cd->ytop)*2./cd->ysize - 1;
glhr::projection_multiply(glhr::translate(tx, -ty, 0));
if(vid.stereo_mode != sODS)
eyewidth_translate(ed);
if(pmodel == mdManual) return;
if(pconf.stretch != 1 && (shader_flags & SF_DIRECT) && pmodel != mdPixel) glhr::projection_multiply(glhr::scale(1, pconf.stretch, 1));
if(vid.stereo_mode != sODS)
eyewidth_translate(ed);
}
auto ortho = [&] (ld x, ld y) {
glhr::glmatrix M = glhr::ortho(x, y, 1);
@ -498,26 +517,44 @@ void display_data::set_projection(int ed, ld shift) {
bool u_alpha = false;
if(shader_flags & SF_PIXELS) ortho(cd->xsize/2, -cd->ysize/2);
if(shader_flags & SF_PIXELS) {
#if CAP_VR
if(vrhr::state == 2) {
glhr::projection_multiply(glhr::tmtogl_transpose(vrhr::hmd_mvp));
glhr::id_modelview();
}
else
#endif
ortho(cd->xsize/2, -cd->ysize/2);
}
else if(shader_flags & SF_BOX) ortho(cd->xsize/current_display->radius/2, -cd->ysize/current_display->radius/2);
else if(shader_flags & SF_ODSBOX) {
ortho(M_PI, M_PI);
glhr::fog_max(1/sightranges[geometry], darkena(backcolor, 0, 0xFF));
}
else if(shader_flags & SF_PERS3) {
glhr::projection_multiply(glhr::frustum(current_display->tanfov, current_display->tanfov * cd->ysize / cd->xsize));
glhr::projection_multiply(glhr::scale(1, -1, -1));
if(nisot::local_perspective_used()) {
if(prod) {
for(int i=0; i<3; i++) NLP[3][i] = NLP[i][3] = 0;
NLP[3][3] = 1;
}
if(!(shader_flags & SF_ORIENT))
glhr::projection_multiply(glhr::tmtogl_transpose(NLP));
#if CAP_VR
if(vrhr::state == 2) {
glhr::projection_multiply(glhr::tmtogl_transpose(vrhr::hmd_mvp));
}
if(ed) {
glhr::using_eyeshift = true;
glhr::eyeshift = glhr::tmtogl(xpush(vid.ipd * ed/2));
#else
if(1) {}
#endif
else {
glhr::projection_multiply(glhr::frustum(current_display->tanfov, current_display->tanfov * cd->ysize / cd->xsize));
glhr::projection_multiply(glhr::scale(1, -1, -1));
if(nisot::local_perspective_used()) {
if(prod) {
for(int i=0; i<3; i++) NLP[3][i] = NLP[i][3] = 0;
NLP[3][3] = 1;
}
if(!(shader_flags & SF_ORIENT))
glhr::projection_multiply(glhr::tmtogl_transpose(NLP));
}
if(ed) {
glhr::using_eyeshift = true;
glhr::eyeshift = glhr::tmtogl(xpush(vid.ipd * ed/2));
}
}
glhr::fog_max(1/sightranges[geometry], darkena(backcolor, 0, 0xFF));
}
@ -644,12 +681,20 @@ EX flagtype get_shader_flags() {
}
EX void glapplymatrix(const transmatrix& V) {
#if CAP_VR
transmatrix V3;
bool use_vr = vrhr::state;
if(use_vr) V3 = vrhr::hmd_pre * V;
const transmatrix& V2 = use_vr ? V3 : V;
#else
const transmatrix& V2 = V;
#endif
GLfloat mat[16];
int id = 0;
if(MXDIM == 3) {
for(int y=0; y<3; y++) {
for(int x=0; x<3; x++) mat[id++] = V[x][y];
for(int x=0; x<3; x++) mat[id++] = V2[x][y];
mat[id++] = 0;
}
mat[12] = 0;
@ -659,7 +704,7 @@ EX void glapplymatrix(const transmatrix& V) {
}
else {
for(int y=0; y<4; y++)
for(int x=0; x<4; x++) mat[id++] = V[x][y];
for(int x=0; x<4; x++) mat[id++] = V2[x][y];
}
glhr::set_modelview(glhr::as_glmatrix(mat));
}

View File

@ -374,6 +374,9 @@ EX struct renderbuffer *airbuf;
EX void make_air() {
if(!sky) return;
#if CAP_VR
if(vrhr::state) return;
#endif
const int AIR_TEXTURE = 512;
if(!airbuf) {
airbuf = new renderbuffer(AIR_TEXTURE, AIR_TEXTURE, true);

View File

@ -224,6 +224,8 @@
#define PSEUDOKEY_WHEELDOWN 2501
#define PSEUDOKEY_WHEELUP 2502
#define PSEUDOKEY_RELEASE 2503
#define PSEUDOKEY_EXIT 2504
#define PSEUDOKEY_MENU 2505
#ifndef CAP_PNG
#define CAP_PNG (!ISMOBWEB)
@ -312,6 +314,10 @@
#define CAP_RACING (!ISMOBWEB && !ISMINI)
#endif
#ifndef CAP_VR
#define CAP_VR ISSTEAM
#endif
#ifndef CAP_LEGACY
#define CAP_LEGACY 0
#endif
@ -462,6 +468,10 @@ typedef unsigned GLuint;
#include <new>
#include <limits.h>
#if CAP_VR
#include "openvr.h"
#endif
#if CAP_VIDEO
#include <sys/wait.h>
#endif

997
vr.cpp Normal file
View File

@ -0,0 +1,997 @@
// 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
enum class eHeadset { none, rotation_only, reference, holonomy };
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::single;
EX cell *forward_cell;
EX ld vraim_x, vraim_y, vrgo_x, vrgo_y;
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."}
};
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 view from the reference point."},
{"single", "(not implemented)"}, // "Display a single monocular image."},
{"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();
}
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 eyepos[2];
vr::TrackedDevicePose_t poses[ vr::k_unMaxTrackedDeviceCount ];
transmatrix pose_matrix[vr::k_unMaxTrackedDeviceCount ];
vector<vr_rendermodel*> models;
vr_rendermodel* device_models[ vr::k_unMaxTrackedDeviceCount ];
controller_data cdata [ vr::k_unMaxTrackedDeviceCount ];
};
vrdata_t vrdata;
/** should we try to access VR */
EX bool enabled = true;
/** 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 computer screen */
EX int state = 0;
// use E4 when working with real-world matrices to ensure that inverses, multiplications, etc. are computed correctly
#define E4 dynamicval<eGeometry> g(geometry, gCubeTiling)
#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";
}
bool first = true;
EX transmatrix hmd_at = Id;
EX transmatrix hmd_ref_at = Id;
EX transmatrix hmd_mvp, hmd_pre;
EX transmatrix sm;
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;
}
#if HDR
struct click {
int x, y, clicked;
};
#endif
EX vector<click> get_hits() {
vector<click> res;
for(auto h: vrhr::vrdata.cdata)
if(h.x || h.y)
res.emplace_back(click{h.x, h.y, h.clicked});
return res;
}
void track_all() {
track_actions();
E4;
sm = Id; sm[1][1] = sm[2][2] = -1;
// println(hlog, "tracking");
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));
}
*/
hyperpoint h1 = sm * hmd_at * vrdata.pose_matrix[i] * sm * C0;
hyperpoint h2 = sm * hmd_at * vrdata.pose_matrix[i] * sm * point31(0, 0, -0.01);
ld p = ilerp(h1[2], h2[2], -ui_depth);
hyperpoint px = lerp(h1, h2, p);
px[0] /= ui_size;
px[1] /= -ui_size;
px[0] += current_display->xsize/2;
px[1] += current_display->ysize/2;
cd.x = px[0];
cd.y = px[1];
}
}
}
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_all();
}
}
EX void be_33(transmatrix& T) {
for(int i=0; i<3; i++) T[i][3] = T[3][i] = 0;
T[3][3] = 1;
}
EX void apply_movement(const transmatrix& rel) {
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);
transmatrix Rot = rel;
be_33(Rot);
rotate_view(Rot);
}
EX void vr_shift() {
if(first) return;
rug::using_rugview urv;
if(GDIM == 2) return;
if(hsm == eHeadset::holonomy) {
apply_movement(IN_E4(hmd_at * inverse(hmd_ref_at)));
hmd_ref_at = hmd_at;
playermoved = false;
if(!rug::rugged) optimizeview();
}
}
EX ld absolute_unit_in_meters = 1;
void move_according_to(vr::ETrackedControllerRole role, bool last, bool cur) {
if(!last && !cur) return;
int id = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(role);
if(id >= 0 && id < int(vr::k_unMaxTrackedDeviceCount)) {
hyperpoint h;
if(true) {
E4;
transmatrix T = (hsm == eHeadset::none ? hmd_at : hmd_ref_at) * vrdata.pose_matrix[id] * sm;
vrhr::be_33(T);
h = T * point31(0, 0, -0.01);
}
if(last && !cur)
movevrdir(h);
else {
movedir md = vectodir(h);
cellwalker xc = cwt + md.d + wstep;
forward_cell = xc.at;
}
}
}
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/menu/in/SelectLeft", [] { return !(cmode && sm::NORMAL); }, [] (bool last, bool curr) {
if(curr && !last) {
int id = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole( vr::TrackedControllerRole_LeftHand);
if(id >= 0 && id < int(vr::k_unMaxTrackedDeviceCount))
vrdata.cdata[id].clicked = true;
}
}),
digital_action_data("/actions/menu/in/SelectRight", [] { return !(cmode && sm::NORMAL); }, [] (bool last, bool curr) {
if(curr && !last) {
int id = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole( vr::TrackedControllerRole_RightHand);
if(id >= 0 && id < int(vr::k_unMaxTrackedDeviceCount))
vrdata.cdata[id].clicked = true;
}
}),
digital_action_data("/actions/menu/in/Exit", [] { return !(cmode && sm::NORMAL); }, [] (bool last, bool curr) {
if(curr && !last) dialog::queue_key(PSEUDOKEY_EXIT);
}),
digital_action_data("/actions/game/in/MoveLeft", [] { return (cmode && sm::NORMAL); }, [] (bool last, bool curr) {
move_according_to(vr::TrackedControllerRole_LeftHand, last, curr);
}),
digital_action_data("/actions/game/in/MoveRight", [] { return (cmode && sm::NORMAL); }, [] (bool last, bool curr) {
move_according_to(vr::TrackedControllerRole_RightHand, last, curr);
}),
digital_action_data("/actions/game/in/EnterMenu", [] { return (cmode && sm::NORMAL); }, [] (bool last, bool curr) {
if(curr && !last) dialog::queue_key(PSEUDOKEY_MENU);
}),
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;
}),
};
vector<set_data> sads = {
set_data("/actions/menu", 20, [] { return !(cmode & sm::NORMAL); }),
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;
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 start_vr() {
vr::EVRInitError eError = vr::VRInitError_None;
vrdata.vr = vr::VR_Init( &eError, vr::VRApplication_Scene );
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");
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++) {
auto eye = vr::EVREye(a);
vrdata.eyes[a] = new vr_framebuffer(vrdata.xsize, vrdata.ysize);
println(hlog, "eye ", a, " : ", vrdata.eyes[a]->ok ? "OK" : "Error");
vrdata.proj[a] =
vr_to_hr(vrdata.vr->GetProjectionMatrix(eye, 0.01, 300));
println(hlog, "projection = ", vrdata.proj[a]);
vrdata.eyepos[a] =
vr_to_hr(vrdata.vr->GetEyeToHeadTransform(eye));
println(hlog, "eye-to-head = ", vrdata.eyepos[a]);
}
//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;
}
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 = 0.004;
EX void in_vr_ui(reaction_t what) {
resetbuffer rb;
if(!state) return;
int xsi = current_display->xsize;
int ysi = current_display->ysize;
state = 2;
for(int i=0; i<2; i++) {
dynamicval<int> vx(vid.xres, vrdata.xsize);
dynamicval<int> vy(vid.yres, vrdata.ysize);
E4;
auto& ey = vrdata.eyes[i];
glBindFramebuffer( GL_FRAMEBUFFER, ey->m_nRenderFramebufferId );
glViewport(0, 0, vrdata.xsize, vrdata.ysize );
calcparam();
glhr::set_depthtest(false);
hmd_mvp = Id;
hmd_mvp = xpush(-xsi/2) * ypush(-ysi/2) * hmd_mvp;
transmatrix Sca = Id;
Sca[0][0] *= ui_size;
Sca[1][1] *= -ui_size;
Sca[2][2] *= 0;
hmd_mvp = Sca * hmd_mvp;
hmd_mvp = zpush(-ui_depth) * hmd_mvp;
hmd_mvp = vrdata.proj[i] * inverse(vrdata.eyepos[i]) * hmd_mvp;
reset_projection();
current_display->set_all(0, 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 render() {
resetbuffer rb;
state = 2;
if(GDIM == 2) {
state = 3;
drawqueue();
return;
}
// eyes = lshiftclick ? eEyes::truesim : eEyes::equidistant;
// cscr = lshiftclick ? eCompScreen::eyes : eCompScreen::single;
for(int i=0; i<2; i++) {
dynamicval<int> vx(vid.xres, vrdata.xsize);
dynamicval<int> vy(vid.yres, vrdata.ysize);
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);
calcparam();
transmatrix mu;
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
mu[i][j] = i!=j ? 0 : i==3 ? 1 : absolute_unit_in_meters;
if(1) {
dynamicval<transmatrix> tN(NLP, NLP);
dynamicval<transmatrix> tV(View, View);
dynamicval<transmatrix> tC(current_display->which_copy, current_display->which_copy);
shiftmatrix Tv = cview();
if(hsm == eHeadset::rotation_only) {
transmatrix T = hmd_at;
be_33(T);
apply_movement(T);
}
else if(hsm == eHeadset::reference) {
apply_movement(IN_E4(hmd_at * inverse(hmd_ref_at)));
}
if(eyes == eEyes::truesim) {
apply_movement(IN_E4(inverse(vrdata.eyepos[i])));
}
make_actual_view();
hmd_pre = cview().T * inverse(Tv.T);
// inverse_shift(Tv, cview());
// View * inverse(Tv.T);
// inverse(inverse_shift(cview(), Tv));
hmd_mvp = Id;
bool nlpu = nisot::local_perspective_used();
if(1) {
E4;
if(nlpu) {
be_33(NLP);
hmd_mvp = NLP * hmd_mvp;
}
hmd_mvp = mu * sm * hmd_mvp;
if(eyes == eEyes::equidistant) {
hmd_mvp = inverse(vrdata.eyepos[i]) * hmd_mvp;
}
hmd_mvp = vrdata.proj[i] * hmd_mvp;
}
}
drawqueue();
}
rb.reset();
calcparam();
current_display->set_viewport(0);
calcparam();
current_display->next_shader_flags = 0;
current_display->set_all(0, 0);
if(cscr == eCompScreen::eyes) draw_eyes();
if(cscr == eCompScreen::single) {
/* todo */
state = 3;
drawqueue();
}
if(cscr == eCompScreen::reference) {
state = 3;
drawqueue();
}
state = 1;
}
template<class T>
void show_choice(string name, T& value, char key, vector<pair<string, string>> options) {
dialog::addSelItem(XLAT(name), XLAT(options[int(value)].first), key);
dialog::add_action_push([&value, name, options] {
dialog::init(XLAT(name));
dialog::addBreak(100);
int q = isize(options);
for(int i=0; i<q; i++) {
dialog::addBoolItem(XLAT(options[i].first), int(value) == i, 'a'+i);
dialog::add_action([&value, i] { value = T(i); popScreen(); });
dialog::addBreak(100);
dialog::addHelp(XLAT(options[i].second));
dialog::addBreak(100);
}
dialog::addBreak(100);
dialog::addBack();
dialog::display();
});
}
EX void show_vr_settings() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen(0);
dialog::init(XLAT("VR settings"));
dialog::addBoolItem_action(XLAT("VR enabled"), enabled, 'o');
if(!enabled)
dialog::addBreak(100);
else if(failed)
dialog::addInfo(XLAT("error: ") + error_msg, 0xC00000);
else
dialog::addInfo(XLAT("VR initialized correctly"), 0x00C000);
dialog::addBreak(100);
show_choice("headset movement", hsm, 'h', headset_desc);
show_choice("binocular vision", eyes, 'b', eyes_desc);
show_choice("computer screen", cscr, 'c', comp_desc);
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 correspond 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)."
));
dialog::scaleLog();
});
if(hsm == eHeadset::reference) {
hyperpoint h = hmd_at * inverse(hmd_ref_at) * C0;
dialog::addSelItem(XLAT("reset the reference point"), state ? fts(hypot_d(3, h)) + "m" : "", 'r');
dialog::add_action([] { hmd_ref_at = hmd_at; });
}
else dialog::addBreak(100);
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("-d:vr")) {
PHASEFROM(2); launch_dialog(show_vr_settings);
}
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() {
addsaver(enabled, "vr-enabled");
addparam(absolute_unit_in_meters, "vr-abs-unit");
}
auto hookc = addHook(hooks_configfile, 100, addconfig);
#endif
EX void submit() {
if(!state) return;
for(int i=0; i<(int)vr::k_unMaxTrackedDeviceCount; i++)
if(vrdata.device_models[i]) {
resetbuffer rb;
if(!state) return;
state = 2;
dynamicval<eModel> m(pmodel, mdPerspective);
dynamicval<ld> ms(sightranges[geometry], 100);
for(int e=0; e<2; e++) {
dynamicval<int> vx(vid.xres, vrdata.xsize);
dynamicval<int> vy(vid.yres, vrdata.ysize);
E4;
auto& ey = vrdata.eyes[e];
glBindFramebuffer( GL_FRAMEBUFFER, ey->m_nRenderFramebufferId );
glViewport(0, 0, vrdata.xsize, vrdata.ysize );
calcparam();
hmd_mvp = vrdata.proj[e] * inverse(vrdata.eyepos[e]) * sm * hmd_at * vrdata.pose_matrix[i] * sm * Id;
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(1) {
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, -10));
glhr::current_vertices = nullptr;
glhr::vertices(vex);
glhr::color2(0xC0FFC0C0);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
}
state = 1;
rb.reset();
calcparam();
current_display->set_viewport(0);
calcparam();
reset_projection();
current_display->set_all(0, 0);
}
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 );
}
}
#endif
EX }
}