nuclear@5: #include nuclear@5: #include nuclear@5: #include nuclear@5: #include nuclear@5: #include nuclear@5: #include "vr.h" nuclear@5: nuclear@5: int init(void); nuclear@5: void cleanup(void); nuclear@5: void toggle_hmd_fullscreen(void); nuclear@5: void display(void); nuclear@5: void draw_scene(void); nuclear@5: void draw_box(float xsz, float ysz, float zsz, float norm_sign); nuclear@5: void update_rtarg(int width, int height); nuclear@5: int handle_event(SDL_Event *ev); nuclear@5: int key_event(int key, int state); nuclear@5: void reshape(int x, int y); nuclear@5: unsigned int next_pow2(unsigned int x); nuclear@5: void quat_to_matrix(const float *quat, float *mat); nuclear@5: unsigned int gen_chess_tex(float r0, float g0, float b0, float r1, float g1, float b1); nuclear@5: nuclear@5: static SDL_Window *win; nuclear@5: static SDL_GLContext ctx; nuclear@5: static int win_width, win_height; nuclear@5: nuclear@5: static unsigned int fbo, fb_tex, fb_depth; nuclear@5: static int fb_width, fb_height; nuclear@5: static int fb_tex_width, fb_tex_height; nuclear@5: nuclear@5: static unsigned int chess_tex; nuclear@5: nuclear@5: nuclear@5: int main(int argc, char **argv) nuclear@5: { nuclear@5: if(init() == -1) { nuclear@5: return 1; nuclear@5: } nuclear@5: nuclear@5: for(;;) { nuclear@5: SDL_Event ev; nuclear@5: while(SDL_PollEvent(&ev)) { nuclear@5: if(handle_event(&ev) == -1) { nuclear@5: goto done; nuclear@5: } nuclear@5: } nuclear@5: display(); nuclear@5: } nuclear@5: nuclear@5: done: nuclear@5: cleanup(); nuclear@5: return 0; nuclear@5: } nuclear@5: nuclear@5: nuclear@5: int init(void) nuclear@5: { nuclear@5: int x, y; nuclear@5: unsigned int flags; nuclear@5: nuclear@5: SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); nuclear@5: nuclear@5: x = y = SDL_WINDOWPOS_UNDEFINED; nuclear@5: flags = SDL_WINDOW_OPENGL; nuclear@5: if(!(win = SDL_CreateWindow("press 'f' to move to the HMD", x, y, 1280, 800, flags))) { nuclear@5: fprintf(stderr, "failed to create window\n"); nuclear@5: return -1; nuclear@5: } nuclear@5: if(!(ctx = SDL_GL_CreateContext(win))) { nuclear@5: fprintf(stderr, "failed to create OpenGL context\n"); nuclear@5: return -1; nuclear@5: } nuclear@5: nuclear@5: glewInit(); nuclear@5: nuclear@5: if(vr_init() == -1) { nuclear@5: return -1; nuclear@5: } nuclear@5: nuclear@5: /* resize our window to match the HMD resolution */ nuclear@11: win_width = vr_geti(VR_DISPLAY_WIDTH); nuclear@11: win_height = vr_geti(VR_DISPLAY_HEIGHT); nuclear@5: if(!win_width || !win_height) { nuclear@5: SDL_GetWindowSize(win, &win_width, &win_height); nuclear@5: } else { nuclear@5: SDL_SetWindowSize(win, win_width, win_height); nuclear@5: SDL_SetWindowPosition(win, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); nuclear@5: } nuclear@5: nuclear@5: /* and create a single render target texture to encompass both eyes */ nuclear@11: fb_width = vr_geti(VR_LEYE_XRES) + vr_geti(VR_REYE_XRES); nuclear@11: fb_height = vr_geti(VR_LEYE_YRES); /* assuming both are the same */ nuclear@5: if(!fb_width || !fb_height) { nuclear@5: fb_width = win_width; nuclear@5: fb_height = win_height; nuclear@5: } nuclear@5: update_rtarg(fb_width, fb_height); nuclear@5: nuclear@5: /* set our render texture and its active area */ nuclear@6: vr_output_texture(fb_tex, 0, 0, (float)fb_width / (float)fb_tex_width, (float)fb_height / (float)fb_tex_height); nuclear@5: nuclear@5: glEnable(GL_DEPTH_TEST); nuclear@5: glEnable(GL_CULL_FACE); nuclear@5: glEnable(GL_LIGHTING); nuclear@5: glEnable(GL_LIGHT0); nuclear@5: glEnable(GL_LIGHT1); nuclear@5: glEnable(GL_NORMALIZE); nuclear@5: nuclear@5: glClearColor(0.5, 0.1, 0.1, 1); nuclear@5: nuclear@5: chess_tex = gen_chess_tex(1.0, 0.7, 0.4, 0.4, 0.7, 1.0); nuclear@5: return 0; nuclear@5: } nuclear@5: nuclear@5: void cleanup(void) nuclear@5: { nuclear@5: vr_shutdown(); nuclear@5: SDL_Quit(); nuclear@5: } nuclear@5: nuclear@5: void toggle_hmd_fullscreen(void) nuclear@5: { nuclear@5: static int fullscr, prev_x, prev_y; nuclear@5: fullscr = !fullscr; nuclear@5: nuclear@5: if(fullscr) { nuclear@5: /* going fullscreen on the rift. save current window position, and move it nuclear@5: * to the rift's part of the desktop before going fullscreen nuclear@5: */ nuclear@5: SDL_GetWindowPosition(win, &prev_x, &prev_y); nuclear@11: SDL_SetWindowPosition(win, vr_geti(VR_WIN_XOFFS), vr_geti(VR_WIN_YOFFS)); nuclear@5: SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP); nuclear@5: } else { nuclear@5: /* return to windowed mode and move the window back to its original position */ nuclear@5: SDL_SetWindowFullscreen(win, 0); nuclear@5: SDL_SetWindowPosition(win, prev_x, prev_y); nuclear@5: } nuclear@5: } nuclear@5: nuclear@5: void display(void) nuclear@5: { nuclear@5: int i; nuclear@8: float proj_mat[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; nuclear@8: float view_mat[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; nuclear@5: nuclear@5: /* start drawing onto our texture render target */ nuclear@5: glBindFramebuffer(GL_FRAMEBUFFER, fbo); nuclear@5: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); nuclear@5: nuclear@5: /* for each eye ... */ nuclear@5: for(i=0; i<2; i++) { nuclear@5: vr_begin(i); nuclear@5: nuclear@5: /* -- viewport transformation -- nuclear@5: * setup the viewport to draw in the left half of the framebuffer when we're nuclear@5: * rendering the left eye's view (0, 0, width/2, height), and in the right half nuclear@5: * of the framebuffer for the right eye's view (width/2, 0, width/2, height) nuclear@5: */ nuclear@5: glViewport(i == 0 ? 0 : fb_width / 2, 0, fb_width / 2, fb_height); nuclear@5: nuclear@5: glMatrixMode(GL_PROJECTION); nuclear@5: /* -- projection transformation -- nuclear@5: * we'll just have to use the projection matrix supplied by the oculus SDK for this eye nuclear@5: * note that libovr matrices are the transpose of what OpenGL expects, so we have to nuclear@5: * use glLoadTransposeMatrixf instead of glLoadMatrixf to load it. nuclear@5: */ nuclear@5: if(vr_proj_matrix(i, 0.5, 500.0, proj_mat)) { nuclear@8: glLoadMatrixf(proj_mat); nuclear@5: } else { nuclear@5: glLoadIdentity(); nuclear@5: gluPerspective(50.0, (float)fb_width / 2.0 / (float)fb_height, 0.5, 500.0); nuclear@5: } nuclear@5: nuclear@5: /* -- view/camera transformation -- nuclear@5: * we need to construct a view matrix by combining all the information provided by the oculus nuclear@5: * SDK, about the position and orientation of the user's head in the world. nuclear@5: */ nuclear@5: glMatrixMode(GL_MODELVIEW); nuclear@5: vr_view_matrix(i, view_mat); nuclear@8: glLoadMatrixf(view_mat); nuclear@5: /* move the camera to the eye level of the user */ nuclear@11: glTranslatef(0, -vr_getf(VR_EYE_HEIGHT), 0); nuclear@5: nuclear@5: /* finally draw the scene for this eye */ nuclear@5: draw_scene(); nuclear@5: nuclear@5: vr_end(); nuclear@5: } nuclear@5: nuclear@5: /* after drawing both eyes into the texture render target, revert to drawing directly to the nuclear@5: * display, and we call ovrHmd_EndFrame, to let the Oculus SDK draw both images properly nuclear@5: * compensated for lens distortion and chromatic abberation onto the HMD screen. nuclear@5: */ nuclear@5: glBindFramebuffer(GL_FRAMEBUFFER, 0); nuclear@5: glViewport(0, 0, win_width, win_height); nuclear@5: nuclear@5: vr_swap_buffers(); nuclear@5: nuclear@5: assert(glGetError() == GL_NO_ERROR); nuclear@5: } nuclear@5: nuclear@5: void draw_scene(void) nuclear@5: { nuclear@5: int i; nuclear@5: float grey[] = {0.8, 0.8, 0.8, 1}; nuclear@5: float col[] = {0, 0, 0, 1}; nuclear@5: float lpos[][4] = { nuclear@5: {-8, 2, 10, 1}, nuclear@5: {0, 15, 0, 1} nuclear@5: }; nuclear@5: float lcol[][4] = { nuclear@5: {0.8, 0.8, 0.8, 1}, nuclear@5: {0.4, 0.3, 0.3, 1} nuclear@5: }; nuclear@5: nuclear@5: for(i=0; i<2; i++) { nuclear@5: glLightfv(GL_LIGHT0 + i, GL_POSITION, lpos[i]); nuclear@5: glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lcol[i]); nuclear@5: } nuclear@5: nuclear@5: glMatrixMode(GL_MODELVIEW); nuclear@5: nuclear@5: glPushMatrix(); nuclear@5: glTranslatef(0, 10, 0); nuclear@5: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, grey); nuclear@5: glBindTexture(GL_TEXTURE_2D, chess_tex); nuclear@5: glEnable(GL_TEXTURE_2D); nuclear@5: draw_box(30, 20, 30, -1.0); nuclear@5: glDisable(GL_TEXTURE_2D); nuclear@5: glPopMatrix(); nuclear@5: nuclear@5: for(i=0; i<4; i++) { nuclear@5: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, grey); nuclear@5: glPushMatrix(); nuclear@5: glTranslatef(i & 1 ? 5 : -5, 1, i & 2 ? -5 : 5); nuclear@5: draw_box(0.5, 2, 0.5, 1.0); nuclear@5: glPopMatrix(); nuclear@5: nuclear@5: col[0] = i & 1 ? 1.0 : 0.3; nuclear@5: col[1] = i == 0 ? 1.0 : 0.3; nuclear@5: col[2] = i & 2 ? 1.0 : 0.3; nuclear@5: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, col); nuclear@5: nuclear@5: glPushMatrix(); nuclear@5: if(i & 1) { nuclear@5: glTranslatef(0, 0.25, i & 2 ? 2 : -2); nuclear@5: } else { nuclear@5: glTranslatef(i & 2 ? 2 : -2, 0.25, 0); nuclear@5: } nuclear@5: draw_box(0.5, 0.5, 0.5, 1.0); nuclear@5: glPopMatrix(); nuclear@5: } nuclear@5: } nuclear@5: nuclear@5: void draw_box(float xsz, float ysz, float zsz, float norm_sign) nuclear@5: { nuclear@5: glMatrixMode(GL_MODELVIEW); nuclear@5: glScalef(xsz * 0.5, ysz * 0.5, zsz * 0.5); nuclear@5: nuclear@5: if(norm_sign < 0.0) { nuclear@5: glFrontFace(GL_CW); nuclear@5: } nuclear@5: nuclear@5: glBegin(GL_QUADS); nuclear@5: glNormal3f(0, 0, 1 * norm_sign); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, -1, 1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(1, -1, 1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(1, 1, 1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(-1, 1, 1); nuclear@5: glNormal3f(1 * norm_sign, 0, 0); nuclear@5: glTexCoord2f(0, 0); glVertex3f(1, -1, 1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(1, -1, -1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(1, 1, -1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(1, 1, 1); nuclear@5: glNormal3f(0, 0, -1 * norm_sign); nuclear@5: glTexCoord2f(0, 0); glVertex3f(1, -1, -1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(-1, -1, -1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(-1, 1, -1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(1, 1, -1); nuclear@5: glNormal3f(-1 * norm_sign, 0, 0); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(-1, -1, 1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(-1, 1, 1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(-1, 1, -1); nuclear@5: glEnd(); nuclear@5: glBegin(GL_TRIANGLE_FAN); nuclear@5: glNormal3f(0, 1 * norm_sign, 0); nuclear@5: glTexCoord2f(0.5, 0.5); glVertex3f(0, 1, 0); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, 1, 1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(1, 1, 1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(1, 1, -1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(-1, 1, -1); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, 1, 1); nuclear@5: glEnd(); nuclear@5: glBegin(GL_TRIANGLE_FAN); nuclear@5: glNormal3f(0, -1 * norm_sign, 0); nuclear@5: glTexCoord2f(0.5, 0.5); glVertex3f(0, -1, 0); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); nuclear@5: glTexCoord2f(1, 0); glVertex3f(1, -1, -1); nuclear@5: glTexCoord2f(1, 1); glVertex3f(1, -1, 1); nuclear@5: glTexCoord2f(0, 1); glVertex3f(-1, -1, 1); nuclear@5: glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); nuclear@5: glEnd(); nuclear@5: nuclear@5: glFrontFace(GL_CCW); nuclear@5: } nuclear@5: nuclear@5: /* update_rtarg creates (and/or resizes) the render target used to draw the two stero views */ nuclear@5: void update_rtarg(int width, int height) nuclear@5: { nuclear@5: if(!fbo) { nuclear@5: /* if fbo does not exist, then nothing does... create every opengl object */ nuclear@5: glGenFramebuffers(1, &fbo); nuclear@5: glGenTextures(1, &fb_tex); nuclear@5: glGenRenderbuffers(1, &fb_depth); nuclear@5: nuclear@5: glBindTexture(GL_TEXTURE_2D, fb_tex); nuclear@5: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); nuclear@5: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); nuclear@5: } nuclear@5: nuclear@5: glBindFramebuffer(GL_FRAMEBUFFER, fbo); nuclear@5: nuclear@5: /* calculate the next power of two in both dimensions and use that as a texture size */ nuclear@5: fb_tex_width = next_pow2(width); nuclear@5: fb_tex_height = next_pow2(height); nuclear@5: nuclear@5: /* create and attach the texture that will be used as a color buffer */ nuclear@5: glBindTexture(GL_TEXTURE_2D, fb_tex); nuclear@5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, nuclear@5: GL_RGBA, GL_UNSIGNED_BYTE, 0); nuclear@5: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_tex, 0); nuclear@5: nuclear@5: /* create and attach the renderbuffer that will serve as our z-buffer */ nuclear@5: glBindRenderbuffer(GL_RENDERBUFFER, fb_depth); nuclear@5: glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, fb_tex_width, fb_tex_height); nuclear@5: glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb_depth); nuclear@5: nuclear@5: if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { nuclear@5: fprintf(stderr, "incomplete framebuffer!\n"); nuclear@5: } nuclear@5: nuclear@5: glBindFramebuffer(GL_FRAMEBUFFER, 0); nuclear@5: printf("created render target: %dx%d (texture size: %dx%d)\n", width, height, fb_tex_width, fb_tex_height); nuclear@5: } nuclear@5: nuclear@5: int handle_event(SDL_Event *ev) nuclear@5: { nuclear@5: switch(ev->type) { nuclear@5: case SDL_QUIT: nuclear@5: return -1; nuclear@5: nuclear@5: case SDL_KEYDOWN: nuclear@5: case SDL_KEYUP: nuclear@5: if(key_event(ev->key.keysym.sym, ev->key.state == SDL_PRESSED) == -1) { nuclear@5: return -1; nuclear@5: } nuclear@5: break; nuclear@5: nuclear@5: default: nuclear@5: break; nuclear@5: } nuclear@5: nuclear@5: return 0; nuclear@5: } nuclear@5: nuclear@5: int key_event(int key, int state) nuclear@5: { nuclear@5: if(state) { nuclear@5: switch(key) { nuclear@5: case 27: nuclear@5: return -1; nuclear@5: nuclear@5: case ' ': nuclear@5: /* allow the user to recenter by pressing space */ nuclear@5: vr_recenter(); nuclear@5: break; nuclear@5: nuclear@5: case 'f': nuclear@5: /* press f to move the window to the HMD */ nuclear@5: toggle_hmd_fullscreen(); nuclear@5: break; nuclear@5: nuclear@5: default: nuclear@5: break; nuclear@5: } nuclear@5: } nuclear@5: return 0; nuclear@5: } nuclear@5: nuclear@5: unsigned int next_pow2(unsigned int x) nuclear@5: { nuclear@5: x -= 1; nuclear@5: x |= x >> 1; nuclear@5: x |= x >> 2; nuclear@5: x |= x >> 4; nuclear@5: x |= x >> 8; nuclear@5: x |= x >> 16; nuclear@5: return x + 1; nuclear@5: } nuclear@5: nuclear@5: /* convert a quaternion to a rotation matrix */ nuclear@5: void quat_to_matrix(const float *quat, float *mat) nuclear@5: { nuclear@5: mat[0] = 1.0 - 2.0 * quat[1] * quat[1] - 2.0 * quat[2] * quat[2]; nuclear@5: mat[4] = 2.0 * quat[0] * quat[1] + 2.0 * quat[3] * quat[2]; nuclear@5: mat[8] = 2.0 * quat[2] * quat[0] - 2.0 * quat[3] * quat[1]; nuclear@5: mat[12] = 0.0f; nuclear@5: nuclear@5: mat[1] = 2.0 * quat[0] * quat[1] - 2.0 * quat[3] * quat[2]; nuclear@5: mat[5] = 1.0 - 2.0 * quat[0]*quat[0] - 2.0 * quat[2]*quat[2]; nuclear@5: mat[9] = 2.0 * quat[1] * quat[2] + 2.0 * quat[3] * quat[0]; nuclear@5: mat[13] = 0.0f; nuclear@5: nuclear@5: mat[2] = 2.0 * quat[2] * quat[0] + 2.0 * quat[3] * quat[1]; nuclear@5: mat[6] = 2.0 * quat[1] * quat[2] - 2.0 * quat[3] * quat[0]; nuclear@5: mat[10] = 1.0 - 2.0 * quat[0]*quat[0] - 2.0 * quat[1]*quat[1]; nuclear@5: mat[14] = 0.0f; nuclear@5: nuclear@5: mat[3] = mat[7] = mat[11] = 0.0f; nuclear@5: mat[15] = 1.0f; nuclear@5: } nuclear@5: nuclear@5: /* generate a chessboard texture with tiles colored (r0, g0, b0) and (r1, g1, b1) */ nuclear@5: unsigned int gen_chess_tex(float r0, float g0, float b0, float r1, float g1, float b1) nuclear@5: { nuclear@5: int i, j; nuclear@5: unsigned int tex; nuclear@5: unsigned char img[8 * 8 * 3]; nuclear@5: unsigned char *pix = img; nuclear@5: nuclear@5: for(i=0; i<8; i++) { nuclear@5: for(j=0; j<8; j++) { nuclear@5: int black = (i & 1) == (j & 1); nuclear@5: pix[0] = (black ? r0 : r1) * 255; nuclear@5: pix[1] = (black ? g0 : g1) * 255; nuclear@5: pix[2] = (black ? b0 : b1) * 255; nuclear@5: pix += 3; nuclear@5: } nuclear@5: } nuclear@5: nuclear@5: glGenTextures(1, &tex); nuclear@5: glBindTexture(GL_TEXTURE_2D, tex); nuclear@5: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); nuclear@5: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); nuclear@5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, img); nuclear@5: nuclear@5: return tex; nuclear@5: }