nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: nuclear@0: bool init(); nuclear@0: void display(); nuclear@0: void draw_grid(); nuclear@1: void draw_frustum(); nuclear@0: void draw_label(const Vector2 &pos, const char *fmt, ...); nuclear@0: void reshape(int x, int y); nuclear@0: void keyb(unsigned char key, int x, int y); nuclear@0: void keyb_up(unsigned char key, int x, int y); nuclear@0: void mouse(int bn, int st, int x, int y); nuclear@0: void motion(int x, int y); nuclear@0: Vector2 screen_to_world(int x, int y); nuclear@1: Vector2 project(const Vector2 &v); nuclear@1: Vector2 unproject(const Vector2 &v); nuclear@2: float calc_proj_dist(float fov); nuclear@0: nuclear@0: int win_xsz, win_ysz; nuclear@0: float aspect; nuclear@0: nuclear@0: Matrix4x4 proj; nuclear@2: float proj_offset; nuclear@0: Vector2 cur_point; nuclear@0: bool cur_point_valid; nuclear@0: nuclear@1: float proj_near = 0.5, proj_far = 50.0; nuclear@2: float proj_vfov = 60.0; nuclear@1: nuclear@0: bool keystate[256]; nuclear@2: bool bnstate[16]; nuclear@2: int prev_x, prev_y; nuclear@2: unsigned int modkeys; nuclear@2: nuclear@0: dtx_font *font; nuclear@2: float pan[2] = {0, 0.85}, zoom = 0.3; nuclear@2: float snap_dist = 0.25; nuclear@0: nuclear@0: int main(int argc, char **argv) nuclear@0: { nuclear@0: glutInit(&argc, argv); nuclear@0: glutInitWindowSize(1024, 768); nuclear@0: glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_MULTISAMPLE); nuclear@0: glutCreateWindow("Interactive projection diagram"); nuclear@0: nuclear@0: glutDisplayFunc(display); nuclear@0: glutReshapeFunc(reshape); nuclear@0: glutKeyboardFunc(keyb); nuclear@0: glutKeyboardUpFunc(keyb_up); nuclear@0: glutMouseFunc(mouse); nuclear@0: glutMotionFunc(motion); nuclear@0: nuclear@0: if(!init()) { nuclear@0: return 1; nuclear@0: } nuclear@0: nuclear@0: glutMainLoop(); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: bool init() nuclear@0: { nuclear@0: if(!(font = dtx_open_font("data/font.ttf", 16))) { nuclear@0: fprintf(stderr, "failed to open font\n"); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: glEnable(GL_MULTISAMPLE); nuclear@1: proj.set_perspective(DEG_TO_RAD(proj_vfov), 1, 1.0, 50.0); nuclear@2: proj.translate(Vector3(proj_offset, 0, 0)); nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: void display() nuclear@0: { nuclear@0: glClear(GL_COLOR_BUFFER_BIT); nuclear@0: nuclear@0: glMatrixMode(GL_MODELVIEW); nuclear@0: glLoadIdentity(); nuclear@0: glTranslatef(-pan[0], -pan[1], 0); nuclear@0: glScalef(zoom, zoom, zoom); nuclear@0: nuclear@0: draw_grid(); nuclear@0: nuclear@1: draw_frustum(); nuclear@1: nuclear@0: if(cur_point_valid) { nuclear@1: Vector2 ppt = project(cur_point); nuclear@1: nuclear@0: glPointSize(7.0); nuclear@0: glBegin(GL_POINTS); nuclear@0: glColor3f(0.2, 1, 0.2); nuclear@0: glVertex2f(cur_point.x, cur_point.y); nuclear@1: glColor3f(0.7, 0.2, 0.2); nuclear@1: glVertex2f(ppt.x, ppt.y); nuclear@0: glEnd(); nuclear@0: nuclear@1: glEnable(GL_LINE_STIPPLE); nuclear@1: glLineStipple(4, 0xe0e0); nuclear@1: nuclear@1: glBegin(GL_LINES); nuclear@1: glColor3f(0.2, 0.5, 0.2); nuclear@1: glVertex2f(cur_point.x, cur_point.y); nuclear@1: glColor3f(0.5, 0.2, 0.2); nuclear@1: glVertex2f(ppt.x, ppt.y); nuclear@1: glEnd(); nuclear@1: glDisable(GL_LINE_STIPPLE); nuclear@1: nuclear@2: glColor3f(0.2, 0.5, 0.2); nuclear@0: draw_label(cur_point, "(%.2f, %.2f)", cur_point.x, cur_point.y); nuclear@2: glColor3f(0.5, 0.2, 0.2); nuclear@1: draw_label(ppt, "(%.2f, %.2f)", ppt.x, ppt.y); nuclear@0: } nuclear@0: nuclear@0: glutSwapBuffers(); nuclear@0: assert(glGetError() == GL_NO_ERROR); nuclear@0: } nuclear@0: nuclear@0: #define LINE(x0, y0, x1, y1) (glVertex2f(x0, y0), glVertex2f(x1, y1)) nuclear@0: nuclear@0: void draw_grid() nuclear@0: { nuclear@1: float ymin = -10; nuclear@1: float ymax = 10; nuclear@1: float xmin = -10; nuclear@1: float xmax = 10; nuclear@1: float ticksz = 0.015 / zoom; nuclear@0: nuclear@0: glBegin(GL_LINES); nuclear@0: glColor3f(1, 1, 1); nuclear@1: LINE(0, -10, 0, 10); nuclear@1: LINE(-10, 0, 10, 0); nuclear@0: nuclear@0: for(int i=1; i<11; i++) { nuclear@0: for(int j=0; j<2; j++) { nuclear@1: float x = 10.0 * (float)i / 10.0 * (j ? -1.0 : 1.0); nuclear@0: nuclear@0: glColor3f(0.15, 0.15, 0.15); nuclear@0: LINE(x, ymin, x, ymax); nuclear@0: LINE(xmin, x, xmax, x); nuclear@0: nuclear@0: glColor3f(0.4, 0.4, 0.4); nuclear@0: LINE(x, -ticksz, x, ticksz); nuclear@0: LINE(-ticksz, x, ticksz, x); nuclear@0: } nuclear@0: } nuclear@0: glEnd(); nuclear@0: } nuclear@0: nuclear@1: void draw_frustum() nuclear@1: { nuclear@2: Vector2 left[2], right[2]; nuclear@1: nuclear@1: glBegin(GL_LINES); nuclear@1: glColor3f(0.2, 0.4, 0.8); nuclear@1: nuclear@2: right[0] = unproject(Vector2(1, 0)); nuclear@2: right[1] = unproject(Vector2(1, 1)); nuclear@2: LINE(right[0].x, right[0].y, right[1].x, right[1].y); nuclear@1: nuclear@2: left[0] = unproject(Vector2(-1, 0)); nuclear@2: left[1] = unproject(Vector2(-1, 1)); nuclear@2: LINE(left[0].x, left[0].y, left[1].x, left[1].y); nuclear@1: nuclear@2: float dist = calc_proj_dist(proj_vfov); nuclear@2: LINE(-1, dist, 1, dist); nuclear@2: glEnd(); nuclear@1: nuclear@2: /* find the points where the frustum lines hit zero */ nuclear@2: glEnable(GL_LINE_STIPPLE); nuclear@2: glLineStipple(3, 0x0101); nuclear@2: nuclear@2: glBegin(GL_LINES); nuclear@2: for(int i=0; i<2; i++) { nuclear@2: Vector2 *v = i ? left : right; nuclear@2: float t = -v[1].y / (v[0].y - v[1].y); nuclear@2: float x = v[1].x + (v[0].x - v[1].x) * t; nuclear@2: LINE(v[0].x, v[0].y, x, 0); nuclear@2: } nuclear@1: glEnd(); nuclear@2: glDisable(GL_LINE_STIPPLE); nuclear@1: } nuclear@1: nuclear@0: void draw_label(const Vector2 &pos, const char *fmt, ...) nuclear@0: { nuclear@0: static char buf[512]; nuclear@0: va_list ap; nuclear@0: nuclear@0: va_start(ap, fmt); nuclear@0: vsnprintf(buf, sizeof buf - 1, fmt, ap); nuclear@0: va_end(ap); nuclear@0: nuclear@0: glPushMatrix(); nuclear@0: glTranslatef(pos.x + 0.01, pos.y + 0.01, 0); nuclear@0: float s = 2.0 / (win_ysz * zoom); nuclear@0: glScalef(s, s, s); nuclear@0: nuclear@0: dtx_string(buf); nuclear@0: nuclear@0: glPopMatrix(); nuclear@0: } nuclear@0: nuclear@0: void reshape(int x, int y) nuclear@0: { nuclear@0: win_xsz = x; nuclear@0: win_ysz = y; nuclear@0: aspect = (float)x / (float)y; nuclear@0: nuclear@0: glViewport(0, 0, x, y); nuclear@0: nuclear@0: glMatrixMode(GL_PROJECTION); nuclear@0: glLoadIdentity(); nuclear@0: glScalef(1.0 / aspect, 1.0, 1.0); nuclear@0: } nuclear@0: nuclear@0: void keyb(unsigned char key, int x, int y) nuclear@0: { nuclear@0: keystate[key] = 1; nuclear@2: modkeys = glutGetModifiers(); nuclear@0: nuclear@0: switch(key) { nuclear@0: case 27: nuclear@0: exit(0); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void keyb_up(unsigned char key, int x, int y) nuclear@0: { nuclear@0: keystate[key] = 0; nuclear@2: modkeys = glutGetModifiers(); nuclear@0: } nuclear@0: nuclear@0: void mouse(int bn, int st, int x, int y) nuclear@0: { nuclear@0: bnstate[bn - GLUT_LEFT_BUTTON] = st == GLUT_DOWN; nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@2: modkeys = glutGetModifiers(); nuclear@0: nuclear@0: if(bn == GLUT_LEFT_BUTTON && st == GLUT_DOWN) { nuclear@0: cur_point = screen_to_world(x, y); nuclear@0: cur_point_valid = true; nuclear@0: glutPostRedisplay(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void motion(int x, int y) nuclear@0: { nuclear@0: float dx = 2.0 * aspect * (x - prev_x) / (float)win_xsz; nuclear@0: float dy = 2.0 * (y - prev_y) / (float)win_ysz; nuclear@0: prev_x = x; nuclear@0: prev_y = y; nuclear@0: nuclear@0: if(bnstate[0]) { nuclear@0: cur_point = screen_to_world(x, y); nuclear@2: if(keystate['s'] || keystate['S']) { nuclear@2: cur_point.x = round(cur_point.x / snap_dist) * snap_dist; nuclear@2: cur_point.y = round(cur_point.y / snap_dist) * snap_dist; nuclear@2: } nuclear@0: glutPostRedisplay(); nuclear@0: } nuclear@0: if(bnstate[1]) { nuclear@0: pan[0] -= dx; nuclear@0: pan[1] += dy; nuclear@0: nuclear@0: glutPostRedisplay(); nuclear@0: } nuclear@0: if(bnstate[2]) { nuclear@0: zoom += dy; nuclear@0: nuclear@0: if(zoom < 1e-4) zoom = 1e-4; nuclear@0: glutPostRedisplay(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: Vector2 screen_to_world(int px, int py) nuclear@0: { nuclear@1: /* canonical [-1, 1] system */ nuclear@1: float x = aspect * ((float)px * 2.0 / (float)win_xsz - 1.0); nuclear@1: float y = (float)(win_ysz - py) * 2.0 / (float)win_ysz - 1.0; nuclear@0: nuclear@1: /* account for pan & zoom */ nuclear@1: x = (x + pan[0]) / zoom; nuclear@1: y = (y + pan[1]) / zoom; nuclear@0: nuclear@0: return Vector2(x, y); nuclear@0: } nuclear@1: nuclear@1: Vector2 project(const Vector2 &v) nuclear@1: { nuclear@1: Vector4 ppt = Vector4(v.x, 0, -v.y, 1).transformed(proj); nuclear@2: return Vector2(ppt.x / ppt.w, calc_proj_dist(proj_vfov)); nuclear@1: } nuclear@1: nuclear@1: Vector2 unproject(const Vector2 &v) nuclear@1: { nuclear@1: Vector4 v4 = Vector4(v.x, 0.0, v.y, 1.0); nuclear@1: Matrix4x4 inv_proj = proj.inverse(); nuclear@1: nuclear@1: Vector4 res4 = v4.transformed(inv_proj); nuclear@1: return Vector2(res4.x / res4.w, -res4.z / res4.w); nuclear@1: } nuclear@2: nuclear@2: float calc_proj_dist(float fov) nuclear@2: { nuclear@2: return 1.0 / tan(DEG_TO_RAD(fov) / 2.0); nuclear@2: }