Orient object along surface normal - opengl

When the user clicks on a surface I would like place an object at this position and orient it perpendicular to the surface normal.
After the user performs a click, I read the depth of three neighboring pixels from the buffer, unproject the pixels from screen coordinates to object space and then compute the surface normal from these points in object space:
glReadPixels(mouseX, mouseY, ..., &depthCenter);
pointCenter = gluUnProject(mouseX, mouseY, depthCenter, ...);
glReadPixels(mouseX, mouseY - 1, ..., &depthUp);
pointUp = gluUnProject(mouseX, mouseY - 1, depthUp, ...);
glReadPixels(mouseX - 1, mouseY, ..., &depthLeft);
pointLeft = gluUnProject(mouseX - 1, mouseY, depthLeft, ...);
centerUpVec = norm( pointCenter - pointUp );
centerLeftVec = norm( pointCenter - pointLeft );
normalVec = norm( centerUpVec.cross(centerLeftVec) );
I know that computing the normal just from three pixels is problematic (e.g. at edges or if the three points have vastly different depth), but for my initial test on a flat surface this must suffice.
Finally, in order to orient the object along the computed normal vector I create a rotation matrix from the normal and the up vector:
upVec = vec(0.0f, 1.0f, 0.0f);
xAxis = norm( upVec.cross(normalVec) );
yAxis = norm( normalVec.cross(xAxis) );
// set orientation of model matrix
modelMat(0,0) = xAxis(0);
modelMat(1,0) = yAxis(0);
modelMat(2,0) = normalVec(0);
modelMat(0,1) = xAxis(1);
modelMat(1,1) = yAxis(1);
modelMat(2,1) = normalVec(1);
modelMat(0,2) = xAxis(2);
modelMat(1,2) = yAxis(2);
modelMat(2,2) = normalVec(2);
// set position of model matrix by using the previously computed center-point
modelMat(0,3) = pointCenter(0);
modelMat(1,3) = pointCenter(1);
modelMat(2,3) = pointCenter(2);
For testing purposes I'm placing an objects on a flat surface after each click. This works well in most cases when my camera is facing downwards the up vector.
However, once I rotate my camera the placed objects are oriented arbitrarily and I can't figure out why!

Ok, I just found a small, stupid bug in my code that was unrelated to the actual problem. Therefore, the approach stated in the question above is working correctly.
In order to avoid some pitfalls, one could of course just use a math library, such as Eigen, in order to compute the rotation between the up vector and the surface normal:
upVec = Eigen::Vector3f(0.0f, 1.0f, 0.0f);
Eigen::Quaternion<float> rotationQuat;
rotationQuat.setFromTwoVectors(upVec, normalVec);

Related

calling glm::unproject() correctly, confused

I'm trying to use glm::unproject() to convert my SDL mouse coordinates into a world position vector, on the x/z-plane. Basically I want to figure out which "x/z" coordinate the user clicked on with a mouse.
From other stack overflow answers I came up needing to call glm::unproject(). I think I'm passing it the wrong arguments, because the values I'm getting back for the world position (printed std std::cerr) aren't world position values as I would expect.
Am I constructing the arguments to glm::unproject() correctly below? Specifically should I be combing the camera's world position and the view matrix (computed using glm::lookAt) to compute the modelview matrix passed into glm::unproject?
struct Dimensions {
int x, y, w, h;
};
glm::mat4
Camera::view_matrix() const
{
// VIEW matrix is created by looking at some target member
auto const& target = target_->translation;
auto const position_xyz = world_position();
glm::vec3 const UP{0, 1, 0};
return glm::lookAt(position_xyz, target, UP);
}
glm::mat4
Camera::projection_matrix() const
{
auto const fov = glm::radians(90.0f);
return glm::perspective(fov, 4.0f/3.0f, 0.1f, 200.0f);
}
glm::vec3
calculate_worldpos(Camera const& camera, int const mouse_x, int const mouse_y)
{
float const width = 1024.0f, height = 768.0f;
glm::vec4 const viewport = glm::vec4(0.0f, 0.0f, width, height);
glm::mat4 const modelview = camera.view_matrix();
glm::mat4 const projection = camera.projection_matrix();
float z = 0.0;
glm::vec3 screenPos = glm::vec3(mouse_x, height - mouse_y - 1, z);
std::cerr << "screenpos: xyz: '" << glm::to_string(screenPos) << "'\n";
glm::vec3 worldPos = glm::unProject(screenPos, modelview, projection, viewport);
std::cerr << "worldpos: xyz: '" << glm::to_string(worldPos) << "'\n";
return worldPos;
}
In the image below, I have the follow setup.
camera lookAt target = (0, 0, 0)
camera world position = (-0.009, 5.107, -0.368)
(mouse_x, mouse_y, mouse_z) = (286, 393, 0)
If you look at the image below, you can see that my mouse is hovering over the world position (3, 0, 0) as shown by the grid. I would expect calculating the world position of my mouse (as shown in the picture) would return me the vector (3, 0, 0). It does not, instead I get the vector: (0.049, 5.007, -0.360).
Does anyone see where I might be going wrong? I'm assuming I'm making some kind of incorrect assumption somewhere.
Your assumption is wrong: glm::unproject returns the worldspace coordinates of the input given by a xy-position in pixel coordinates and a z-coordinate storing the depth value. On every pixel on the screen, there is an infinite number of points in worldspace that project to this pixel (All that lie on the ray going from the projecting center through this pixel). Which one you want is identified by choosing the depth coordinate which than results in one specific point on this ray. Choosing z = 0 means that the result will always be a point on the near-plane of the camera.
What you are actually looking for is the intersection of this ray (going through the camera position and the calculated point) and the xz-plane (where y=0).
The ray is given by the two points on it (camera position C, near plane point P) as follows:
-0.009 0.058
C + l * (P-C) = ( 5.107 ) + l * ( -0.100 )
-0.368 0.008
, where l is a free variable.
As already said, we are looking for the intersection point (a,b) with the y=0 plane, thus we can formulate the following equation:
-0.009 0.058 a
( 5.107 ) + l * ( -0.100 ) = ( 0 )
-0.368 0.008 b
Solving the y-equation (5.107 + l * -0.1 = 0) for l results in l = 51.07. Pasting back in the equations for x and z yields:
a = -0.009 + 51.07 * 0.058 = 2.95306
b = -0.368 + 51.07 * 0.008 = 0.04056
Which is close to the expected worldspace position. The difference is most probably given by the fact that you just showed rounded numbers in the question. For accuracy reasons, I would also not calculate a point on the near-plane but one on the far plane (z=1) since the near-plane distance is usually quite small and could lead to numerical issues.
Conclusion: All values supplied are correct, but you were just not calculating what you expected.

Precision issue - viewpoint far from origin - OpenGL C++

I have a camera class for controlling the camera, with the main function:
void PNDCAMERA::renderMatrix()
{
float dttime=getElapsedSeconds();
GetCursorPos(&cmc.p_cursorPos);
ScreenToClient(hWnd, &cmc.p_cursorPos);
double d_horangle=((double)cmc.p_cursorPos.x-(double)cmc.p_origin.x)/(double)screenWidth*PI;
double d_verangle=((double)cmc.p_cursorPos.y-(double)cmc.p_origin.y)/(double)screenHeight*PI;
cmc.horizontalAngle=d_horangle+cmc.d_horangle_prev;
cmc.verticalAngle=d_verangle+cmc.d_verangle_prev;
if(cmc.verticalAngle>PI/2) cmc.verticalAngle=PI/2;
if(cmc.verticalAngle<-PI/2) cmc.verticalAngle=-PI/2;
changevAngle(cmc.verticalAngle);
changehAngle(cmc.horizontalAngle);
rightVector=glm::vec3(sin(horizontalAngle - PI/2.0f),0,cos(horizontalAngle - PI/2.0f));
directionVector=glm::vec3(cos(verticalAngle) * sin(horizontalAngle), sin(verticalAngle), cos(verticalAngle) * cos(horizontalAngle));
upVector=glm::vec3(glm::cross(rightVector,directionVector));
glm::normalize(upVector);
glm::normalize(directionVector);
glm::normalize(rightVector);
if(moveForw==true)
{
cameraPosition=cameraPosition+directionVector*(float)C_SPEED*dttime;
}
if(moveBack==true)
{
cameraPosition=cameraPosition-directionVector*(float)C_SPEED*dttime;
}
if(moveRight==true)
{
cameraPosition=cameraPosition+rightVector*(float)C_SPEED*dttime;
}
if(moveLeft==true)
{
cameraPosition=cameraPosition-rightVector*(float)C_SPEED*dttime;
}
glViewport(0,0,screenWidth,screenHeight);
glScissor(0,0,screenWidth,screenHeight);
projection_matrix=glm::perspective(60.0f, float(screenWidth) / float(screenHeight), 1.0f, 40000.0f);
view_matrix = glm::lookAt(
cameraPosition,
cameraPosition+directionVector,
upVector);
gShader->bindShader();
gShader->sendUniform4x4("model_matrix",glm::value_ptr(model_matrix));
gShader->sendUniform4x4("view_matrix",glm::value_ptr(view_matrix));
gShader->sendUniform4x4("projection_matrix",glm::value_ptr(projection_matrix));
gShader->sendUniform("camera_position",cameraPosition.x,cameraPosition.y,cameraPosition.z);
gShader->sendUniform("screen_size",(GLfloat)screenWidth,(GLfloat)screenHeight);
};
It runs smooth, I can control the angle with my mouse in X and Y directions, but not around the Z axis (the Y is the "up" in world space).
In my rendering method I render the terrain grid with one VAO call. The grid itself is a quad as the center (highes lod), and the others are L shaped grids scaled by powers of 2. It is always repositioned before the camera, scaled into world space, and displaced by a heightmap.
rcampos.x = round((camera_position.x)/(pow(2,6)*gridscale))*(pow(2,6)*gridscale);
rcampos.y = 0;
rcampos.z = round((camera_position.z)/(pow(2,6)*gridscale))*(pow(2,6)*gridscale);
vPos = vec3(uv.x,0,uv.y)*pow(2,LOD)*gridscale + rcampos;
vPos.y = texture(hmap,vPos.xz/horizontal_scale).r*vertical_scale;
The problem:
The camera starts at the origin, at (0,0,0). When I move it far away from that point, it causes the rotation along the X axis discontinuous. It feels like the mouse cursor was aligned with a grid in screen space, and only the position at grid points were recorded as the cursor movement.
I've also recorded the camera position when it gets pretty noticeable, it's about at 1,000,000 from the origin in X or Z directions. I've noticed that this 'lag' increases linearly with distance, (from the origin).
There is also a little Z-fighting at this point(or similar effect), even if I use a single plane with no displacement, and no planes can overlap. (I use tessellation shaders and render patches.) Black spots appear on the patches. May be caused by fog:
float fc = (view_matrix*vec4(Pos,1)).z/(view_matrix*vec4(Pos,1)).w;
float fResult = exp(-pow(0.00005f*fc, 2.0));
fResult = clamp(fResult, 0.0, 1.0);
gl_FragColor = vec4(mix(vec4(0.0,0.0,0.0,0),vec4(n,1),fResult));
Another strange behavior is the little rotation by the Z axis, this increases with distance too, but I don't use this kind of rotation.
Variable formats:
The vertices are unsigned short format, the indexes are in unsigned int format.
The cmc struct is the camera/cursor struct with double variables.
PI and C_SPEED are #define constants.
Additional information:
The grid is created with the above mentioned ushort array, with the spacing of 1. In the shader I scale it with a constant, then use tessellation to achieve the best performance and the largest view distance.
The final position of a vertex is calculated in the tessellation evaluation shader.
mat4 MVP = projection_matrix*view_matrix*model_matrix;
As you could see I send my matrices to the shader with the glm library.
+Q:
How could the length of a float (or any other format) cause this kind of 'precision loss', or whatever causes the problem. The view_matrix could be a cause of this, but I still cannot output it on the screen at runtime.
PS: I don't know If this helps, but the view matrix at about the 'lag start location' is
-0.49662 -0.49662 0.863129 0
0.00514956 0.994097 0.108373 0
-0.867953 0.0582648 -0.493217 0
1.62681e+006 16383.3 -290126 1
EDIT
Comparing the camera position and view matrix:
view matrix = 0.967928 0.967928 0.248814 0
-0.00387854 0.988207 0.153079 0
-0.251198 -0.149134 0.956378 0
-2.88212e+006 89517.1 -694945 1
position = 2.9657e+006, 6741.52, -46002
It's a long post so I might not answer everything.
I think it is most likely precision issue. Lets start with the camera rotation problem. I think the main problem is here
view_matrix = glm::lookAt(
cameraPosition,
cameraPosition+directionVector,
upVector);
As you said, position is quite a big number like 2.9657e+006 - and look what glm does in glm::lookAt:
GLM_FUNC_QUALIFIER detail::tmat4x4<T> lookAt
(
detail::tvec3<T> const & eye,
detail::tvec3<T> const & center,
detail::tvec3<T> const & up
)
{
detail::tvec3<T> f = normalize(center - eye);
detail::tvec3<T> u = normalize(up);
detail::tvec3<T> s = normalize(cross(f, u));
u = cross(s, f);
In your case, eye and center are these big (very similar) numbers and then glm subtracts them to compute f. This is bad, because if you subtract two almost equal floats, the most significant digits are set to zero, which leaves you with the insignificant (most erroneous) digits. And you use this for further computations, which only emphasizes the error. Check this link for some details.
The z-fighting is similar issue. Z-buffer is not linear, it has the best resolution near the camera because of the perspective divide. The z-buffer range is set according to your near and far clipping plane values. You always want to have the smallest possible ration between far and near values (generally far/near should not be greater than 30000). There is a very good explanation of this on the openGL wiki, I suggest you read it :)
Back to the camera issue - first, I would consider if you really need such a huge scene. I don't think so, but if yes, you could try computing your view matrix differently, compute rotation and translation separately, which could help your case. The way I usually handle camera:
glm::vec3 cameraPos;
glm::vec3 cameraRot;
glm::vec3 cameraPosLag;
glm::vec3 cameraRotLag;
int ox, oy;
const float inertia = 0.08f; //mouse inertia
const float rotateSpeed = 0.2f; //mouse rotate speed (sensitivity)
const float walkSpeed = 0.25f; //walking speed (wasd)
void updateCameraViewMatrix() {
//camera inertia
cameraPosLag += (cameraPos - cameraPosLag) * inertia;
cameraRotLag += (cameraRot - cameraRotLag) * inertia;
// view transform
g_CameraViewMatrix = glm::rotate(glm::mat4(1.0f), cameraRotLag[0], glm::vec3(1.0, 0.0, 0.0));
g_CameraViewMatrix = glm::rotate(g_CameraViewMatrix, cameraRotLag[1], glm::vec3(0.0, 1.0, 0.0));
g_CameraViewMatrix = glm::translate(g_CameraViewMatrix, cameraPosLag);
}
void mousePositionChanged(int x, int y) {
float dx, dy;
dx = (float) (x - ox);
dy = (float) (y - oy);
ox = x;
oy = y;
if (mouseRotationEnabled) {
cameraRot[0] += dy * rotateSpeed;
cameraRot[1] += dx * rotateSpeed;
}
}
void keyboardAction(int key, int action) {
switch (key) {
case 'S':// backwards
cameraPos[0] -= g_CameraViewMatrix[0][2] * walkSpeed;
cameraPos[1] -= g_CameraViewMatrix[1][2] * walkSpeed;
cameraPos[2] -= g_CameraViewMatrix[2][2] * walkSpeed;
break;
...
}
}
This way, the position would not affect your rotation. I should add that I adapted this code from NVIDIA CUDA samples v5.0 (Smoke Particles), I really like it :)
Hope at least some of this helps.

OpenGL Frustum visibility test with sphere : Far plane not working

I am doing a program to test sphere-frustum intersection and being able to determine the sphere's visibility. I am extracting the frustum's clipping planes into camera space and checking for intersection. It works perfectly for all planes except the far plane and I cannot figure out why. I keep pulling the camera back but my program still claims the sphere is visible, despite it having been clipped long ago. If I go far enough it eventually determines that it is not visible, but this is some distance after it has exited the frustum.
I am using a unit sphere at the origin for the test. I am using the OpenGL Mathematics (GLM) library for vector and matrix data structures and for its built in math functions. Here is my code for the visibility function:
void visibilityTest(const struct MVP *mvp) {
static bool visLastTime = true;
bool visThisTime;
const glm::vec4 modelCenter_worldSpace = glm::vec4(0,0,0,1); //at origin
const int negRadius = -1; //unit sphere
//Get cam space model center
glm::vec4 modelCenter_cameraSpace = mvp->view * mvp->model * modelCenter_worldSpace;
//---------Get Frustum Planes--------
//extract projection matrix row vectors
//NOTE: since glm stores their mats in column-major order, we extract columns
glm::vec4 rowVec[4];
for(int i = 0; i < 4; i++) {
rowVec[i] = glm::vec4( mvp->projection[0][i], mvp->projection[1][i], mvp->projection[2][i], mvp->projection[3][i] );
}
//determine frustum clipping planes (in camera space)
glm::vec4 plane[6];
//NOTE: recall that indices start at zero. So M4 + M3 will be rowVec[3] + rowVec[2]
plane[0] = rowVec[3] + rowVec[2]; //near
plane[1] = rowVec[3] - rowVec[2]; //far
plane[2] = rowVec[3] + rowVec[0]; //left
plane[3] = rowVec[3] - rowVec[0]; //right
plane[4] = rowVec[3] + rowVec[1]; //bottom
plane[5] = rowVec[3] - rowVec[1]; //top
//extend view frustum by 1 all directions; near/far along local z, left/right among local x, bottom/top along local y
// -Ax' -By' -Cz' + D = D'
plane[0][3] -= plane[0][2]; // <x',y',z'> = <0,0,1>
plane[1][3] += plane[1][2]; // <0,0,-1>
plane[2][3] += plane[2][0]; // <-1,0,0>
plane[3][3] -= plane[3][0]; // <1,0,0>
plane[4][3] += plane[4][1]; // <0,-1,0>
plane[5][3] -= plane[5][1]; // <0,1,0>
//----------Determine Frustum-Sphere intersection--------
//if any of the dot products between model center and frustum plane is less than -r, then the object falls outside the view frustum
visThisTime = true;
for(int i = 0; i < 6; i++) {
if( glm::dot(plane[i], modelCenter_cameraSpace) < static_cast<float>(negRadius) ) {
visThisTime = false;
}
}
if(visThisTime != visLastTime) {
printf("Sphere is %s visible\n", (visThisTime) ? "" : "NOT " );
visLastTime = visThisTime;
}
}
The polygons appear to be clipped by the far plane properly so it seems that the projection matrix is set up properly, but the calculations make it seem like the plane is way far out. Perhaps I am not calculating something correctly or have a fundamental misunderstanding of the calculations that are required?
The calculations that deal specifically with the far clipping plane are:
plane[1] = rowVec[3] - rowVec[2]; //far
and
plane[1][3] += plane[1][2]; // <0,0,-1>
I'm setting the plane to be equal to the 4th row (or in this case column) of the projection matrix - the 3rd row of the projection matrix. Then I'm extending the far plane one unit further (due to the sphere's radius of one; D' = D - C(-1) )
I've looked over this code many times and I can't see why it shouldn't work. Any help is appreciated.
EDIT:
I can't answer my own question as I don't have the rep, so I will post it here.
The problem was that I wasn't normalizing the plane equations. This didn't seem to make much of a difference for any of the clip planes besides the far one, so I hadn't even considered it (but that didn't make it any less wrong). After normalization everything works properly.

3d model to fit in viewport

How does a 3D model handled unit wise ?
When i have a random model that i want to fit in my view port i dunno if it is too big or not, if i need to translate it to be in the middle...
I think a 3d object might have it's own origine.
You need to find a bounding volume, a shape that encloses all the object's vertices, for your object that is easier to work with than the object itself. Spheres are often used for this. Either the artist can define the sphere as part of the model information or you can work it out at run time. Calculating the optimal sphere is very hard, but you can get a good approximation using the following:
determine the min and max value of each point's x, y and z
for each vertex
min_x = min (min_x, vertex.x)
max_x = max (max_x, vertex.x)
min_y = min (min_y, vertex.y)
max_y = max (max_y, vertex.y)
min_z = min (min_z, vertex.z)
max_z = max (max_z, vertex.z)
sphere centre = (max_x + min_x) / 2, (max_y + min_y) / 2, (max_z + min_z) / 2
sphere radius = distance from centre to (max_x, max_y, max_z)
Using this sphere, determine the a world position that allows the sphere to be viewed in full - simple geometry will determine this.
Sorry, your question is very unclear. I suppose you want to center a 3D model to a viewport. You can achieve this by calculating the model's bounding box. To do this, traverse all polygons and get the minimum/maximum X/Y/Z coordinates. The bounding box given by the points (min_x,min_y,min_z) and (max_x,max_y,max_z) will contain the whole model. Now you can center the model by looking at the center of this box. With some further calculations (depending on your FOV) you can also get the left/right/upper/lower borders inside your viewport.
"so i tried to scale it down"
The best thing to do in this situation is not to transform your model at all! Leave it be. What you want to change is your camera.
First calculate the bounding box of your model somewhere in 3D space.
Next calculate the radius of it by taking the max( aabb.max.x-aabb.min.x, aabb.max.y-aabb.min.y, aabb.max.z-aabb.min.z ). It's crude but it gets the job done.
To center the object in the viewport place the camera at the object position. If Y is your forward axis subtract the radius from Y. If Z is the forward axis then subtract radius from it instead. Subtract a fudge factor to get you past the pesky near plane so your model doesn't clip out. I use quaternions in my engine with a nice lookat() method. So call lookat() and pass in the center of the bounding box. Voila! You're object is centered in the viewport regardless of where it is in the world.
This always places the camera axis aligned so you might want to get fancy and transform the camera into model space instead, subtract off the radius, then lookat() the center again. Then you're always looking at the back of the model. The key is always the lookat().
Here's some example code from my engine. It checks to see if we're trying to frame a chunk of static terrain, if so look down from a height, or a light or a static mesh. A visual is anything that draws in the scene and there are dozens of different types. A Visual::Instance is a copy of the visual, or where to draw it.
void EnvironmentView::frameSelected(){
if( m_tSelection.toInstance() ){
Visual::Instance& I = m_tSelection.toInstance().cast();
Visual* pVisual = I.toVisual();
if( pVisual->isa( StaticTerrain::classid )){
toEditorCamera().toL2W().setPosition( pt3( 0, 0, 50000 ));
toEditorCamera().lookat( pt3( 0 ));
}else if( I.toFlags()->bIsLight ){
Visual::LightInstance& L = static_cast<Visual::LightInstance&>( I );
qst3& L2W = L.toL2W();
const sphere s( L2W.toPosition(), L2W.toScale() );
const f32 y =-(s.toCenter()+s.toRadius()).y();
const f32 z = (s.toCenter()+s.toRadius()).y();
qst3& camL2W = toEditorCamera().toL2W();
camL2W.setPosition(s.toCenter()+pt3( 0, y, z ));//45 deg above
toEditorCamera().lookat( s.toCenter() );
}else{
Mesh::handle hMesh = pVisual->getMesh();
if( hMesh ){
qst3& L2W = m_tSelection.toInstance()->toL2W();
vec4x4 M;
L2W.getMatrix( M );
aabb3 b0 = hMesh->toBounds();
b0.min = M * b0.min;
b0.max = M * b0.max;
aabb3 b1;
b1 += b0.min;
b1 += b0.max;
const sphere s( b1.toSphere() );
const f32 y =-(s.toCenter()+s.toRadius()*2.5f).y();
const f32 z = (s.toCenter()+s.toRadius()*2.5f).y();
qst3& camL2W = toEditorCamera().toL2W();
camL2W.setPosition( L2W.toPosition()+pt3( 0, y, z ));//45 deg above
toEditorCamera().lookat( b1.toOrigin() );
}
}
}
}

Some faces are transparent, other are opaque

I have created a regular dodecahedron with OpenGL. I wanted to make the faces transparent (as in the image on Wikipedia) but this doesn't always work. After some digging in the OpenGL documentation, is appears that I "need to sort the transparent faces from back to front". Hm. How do I do that?
I mean I call glRotatef() to rotate the coordinate system but the reference coordinates of the faces stay the same; the rotation effect is applied "outside" of my renering code.
If I apply the transformation to the coordinates, then everything else will stop moving.
How can I sort the faces in this case?
[EDIT] I know why this happens. I have no idea what the solution could look like. Can someone please direct me to the correct OpenGL calls or a piece of sample code? I know when the coordinate transform is finished and I have the coordinates of the vertices of the faces. I know how to calculate the center coordinates of the faces. I understand that I need to sort them by Z value. How to I transform a Vector3f by the current view matrix (or whatever this thing is called that rotates my coordinate system)?
Code to rotate the view:
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
When the OpenGL documentation says "sort the transparent faces" it means "change the order in which you draw them". You don't transform the geometry of the faces themselves, instead you make sure that you draw the faces in the right order: farthest from the camera first, nearest to the camera last, so that the colour is blended correctly in the frame buffer.
One way to do this is to compute for each transparent face a representative distance from the camera (for example, the distance of its centre from the centre of the camera), and then sort the list of transparent faces on this representative distance.
You need to do this because OpenGL uses the Z-buffering technique.
(I should add that the technique of "sorting by the distance of the centre of the face" is a bit naive, and leads to the wrong result in cases where faces are large or close to the camera. But it's simple and will get you started; there'll be plenty of time later to worry about more sophisticated approaches to Z-sorting.)
Update: Aaron, you clarified the post to indicate that you understand the above, but don't know how to calculate a suitable Z value for each face. Is that right? I would usually do this by measuring the distance from the camera to the face in question. So I guess this means you don't know where the camera is?
If that's a correct statement of the problem you're having, see OpenGL FAQ 8.010:
As far as OpenGL is concerned, there is no camera. More specifically, the camera is always located at the eye space coordinate (0., 0., 0.).
Update: Maybe the problem is that you don't know how to transform a point by the modelview matrix? If that's the problem, see OpenGL FAQ 9.130:
Transform the point into eye-coordinate space by multiplying it by the ModelView matrix. Then simply calculate its distance from the origin.
Use glGetFloatv(GL_MODELVIEW_MATRIX, dst) to get the modelview matrix as a list of 16 floats. I think you'll have to do the multiplication yourself: as far as I know OpenGL doesn't provide an API for this.
For reference, here is the code (using lwjgl 2.0.1). I define my model by using an array of float arrays for the coordinates:
float one = 1f * scale;
// Cube of size 2*scale
float[][] coords = new float[][] {
{ one, one, one }, // 0
{ -one, one, one },
{ one, -one, one },
{ -one, -one, one },
{ one, one, -one },
{ -one, one, -one },
{ one, -one, -one },
{ -one, -one, -one }, // 7
};
Faces are defined in an array of int arrays. The items in the inner array are indices of vertices:
int[][] faces = new int[][] {
{ 0, 2, 3, 1, },
{ 0, 4, 6, 2, },
{ 0, 1, 5, 4, },
{ 4, 5, 7, 6, },
{ 5, 1, 3, 7, },
{ 4, 5, 1, 0, },
};
These lines load the Model/View matrix:
Matrix4f matrix = new Matrix4f ();
FloatBuffer params = FloatBuffer.allocate (16);
GL11.glGetFloat (GL11.GL_MODELVIEW_MATRIX, params );
matrix.load (params);
I store some information of each face in a Face class:
public static class Face
{
public int id;
public Vector3f center;
#Override
public String toString ()
{
return String.format ("%d %.2f", id, center.z);
}
}
This comparator is then used to sort the faces by Z depth:
public static final Comparator<Face> FACE_DEPTH_COMPARATOR = new Comparator<Face> ()
{
#Override
public int compare (Face o1, Face o2)
{
float d = o1.center.z - o2.center.z;
return d < 0f ? -1 : (d == 0 ? 0 : 1);
}
};
getCenter() returns the center of a face:
public static Vector3f getCenter (float[][] coords, int[] face)
{
Vector3f center = new Vector3f ();
for (int vertice = 0; vertice < face.length; vertice ++)
{
float[] c = coords[face[vertice]];
center.x += c[0];
center.y += c[1];
center.z += c[2];
}
float N = face.length;
center.x /= N;
center.y /= N;
center.z /= N;
return center;
}
Now I need to set up the face array:
Face[] faceArray = new Face[faces.length];
Vector4f v = new Vector4f ();
for (int f = 0; f < faces.length; f ++)
{
Face face = faceArray[f] = new Face ();
face.id = f;
face.center = getCenter (coords, faces[f]);
v.x = face.center.x;
v.y = face.center.y;
v.z = face.center.z;
v.w = 0f;
Matrix4f.transform (matrix, v, v);
face.center.x = v.x;
face.center.y = v.y;
face.center.z = v.z;
}
After this loop, I have the transformed center vectors in faceArray and I can sort them by Z value:
Arrays.sort (faceArray, FACE_DEPTH_COMPARATOR);
//System.out.println (Arrays.toString (faceArray));
Rendering happens in another nested loop:
float[] faceColor = new float[] { .3f, .7f, .9f, .3f };
for (Face f: faceArray)
{
int[] face = faces[f.id];
glColor4fv(faceColor);
GL11.glBegin(GL11.GL_TRIANGLE_FAN);
for (int vertice = 0; vertice < face.length; vertice ++)
{
glVertex3fv (coords[face[vertice]]);
}
GL11.glEnd();
}
Have you tried just drawing each face, in relation to regular world coordinates from back to front? Often it seems like the wording in some of the OpenGL docs is weird. I think if you get the drawing in the right order with out worrying about rotation, it might automatically work when you add rotation. OpenGL might take care of the reordering of faces when rotating the matrix.
Alternatively you can grab the current matrix as you draw ( glGetMatrix() ) and reorder your drawing algorithm depending on which faces are going to be the rotated back/front.
That quote says it all - you need to sort the faces.
When drawing such a simple object you can just render the back faces first and the front faces second using the z-buffer (by rendering twice with different z-buffer comparison functions).
But usually, you just want to transform the object, then sort the faces. You transform just your representation of the object in memory, then determine the drawing order by sorting, then draw in that order with the original coordinates, using transformations as needed (need to be consistent with the sorting you've done). In a real application, you would probably do the transformation implicitly, eg. by storing the scene as a BSP- or Quad- or R- or whatever-tree and simply traversing the tree from various directions.
Note that the sorting part can be tricky, because the function "is-obsucred-by" which is the function you want to compare the faces by (because you need to draw the obscured faces first) is not an ordering, eg. there can be cycles (face A obscures B && face B obscures A). In this case, you would probably split one of the faces to break the loop.
EDIT:
You get the z-coordinate of a vertex by taking the coordinates you pass to glVertex3f(), make it 4D (homogenous coordinates) by appending 1, transform it with the modelview matrix, then transform it with the projection matrix, then do the perspective division. The details are in the OpenGL specs in Chapter 2, section Coordinate transformations.
However, there isn't any API for you to actually do the transformation. The only thing OpenGL lets you do is to draw the primitives, and tell the renderer how to draw them (eg. how to transform them). It doesn't let you easily transform coordinates or anything else (although there IIUC are ways to tell OpenGL to write transformed coordinates to a buffer, this is not that easy). If you want some library to help you manipulate actual objects, coordinates etc., consider using some sort of scenegraph library (OpenInventor or something)