Here's some simple ports of the stuff you don't get in GLES 2 (or, eventually, an OpenGL 4.x world):
void graphics_t::set_projection_2D(int width,int height) {
matrix_t projection = {{ // transposed for OpenGL
2./width, 0, 0, 0,
0, 2./-height, 0, 0,
0, 0, -2, 0,
-1, 1, -1, 1
}};
set_projection_matrix(projection);
}
void graphics_t::set_projection_perspective(GLfloat fovy,GLfloat aspect,GLfloat zNear,GLfloat zFar) {
const GLfloat
radians = fovy / 2. * M_PI / 180.,
sine = sin(radians),
deltaZ = zNear - zFar;
graphics_assert((deltaZ!=0)&&(sine!=0)&&(aspect!=0));
const GLfloat cotangent = cos(radians) / sine;
const matrix_t perspective = {{
cotangent / aspect, 0, 0, 0,
0, cotangent, 0, 0,
0, 0, (zFar + zNear) / deltaZ, -1,
0, 0, (2. * zNear * zFar) / deltaZ, 0
}};
set_projection_matrix(perspective);
#if !defined(GLES2) && !defined(NDEBUG)
matrix_t check;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fovy,aspect,zNear,zFar);
glGetFloatv(GL_PROJECTION_MATRIX,check.f);
if(pimpl->projection != check)
graphics_error("Error setting perspective: " << std::endl << pimpl->projection << std::endl << " != " << std::endl << check);
#endif
}
void graphics_t::set_modelview_look_at(const vec_t& eye,const vec_t& centre,const vec_t& up) {
#if !defined(GLES2) && !defined(NDEBUG)
matrix_t check;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eye.x,eye.y,eye.z,centre.x,centre.y,centre.z,up.x,up.y,up.z);
glGetFloatv(GL_MODELVIEW_MATRIX,check.f);
#endif
const vec_t f((centre-eye).normalised()),
s(f.cross(up).normalised()),
u(s.cross(f).normalised());
const matrix_t m = {{
s.x,u.x,-f.x,0,
s.y,u.y,-f.y,0,
s.z,u.z,-f.z,0,
0,0,0,1
}};
set_modelview_matrix(matrix_t::translation(-eye).transposition() * m);
#if !defined(GLES2) && !defined(NDEBUG)
graphics_assert(pimpl->modelview == check);
#endif
}
vec_t graphics_t::unproject(const vec_t& win,GLint viewport[4]) {
#if !defined(GLES2) && !defined(NDEBUG)
double p[16], mv[16], c[3];
for(int i=0; i<16; i++) {
p[i] = pimpl->projection.f[i];
mv[i] = pimpl->modelview.f[i];
}
gluUnProject(win.x,win.y,win.z,mv,p,viewport,c,c+1,c+2);
const vec_t check(c[0],c[1],c[2]);
#endif
const vec4_t in(
(win.x - viewport[0]) / viewport[2] * 2. - 1.,
(win.y - viewport[1]) / viewport[3] * 2. - 1.,
2. * win.z - 1.,
1.),
inv = in*pimpl->modelviewprojection.inverse().transposition(); // from OpenGL to normal order
const vec_t out(inv.x/inv.w,inv.y/inv.w,inv.z/inv.w);
#if !defined(GLES2) && !defined(NDEBUG)
if(fabs(out.x-check.x)>0.0001 || // fuzzy to account for using floats not doubles
fabs(out.y-check.y)>0.0001 ||
fabs(out.z-check.z)>0.0001)
std::cout << "WARNING! unproject failed: " << out << " != " << check << std::endl;
#endif
return out;
}
As you can see, the functions check their results against the classic functions they are replacing so I've got good confidence in them nowadays.
For selection, for example, you can unproject the near z (win.z == 0) and the far z (win.z == 1) and this gives you a ray through the scene on the mouse-click.
Because Glest is using a grid, you can then use a Bresenham's to march across the map, foreground to background order, checking to see if there's an object on the intersecting tile to be tested.
Even without bothering to cache the inverse projection matrix there really isn't any performance impact in doing this every frame; this is infinitesimal effort compared to doing any rendering at all.
Which reminds me; it would be so cool if a debug-mode showed models that spilled out over the boundaries of their tiles.