Centre the scene on the screen

Started by
14 comments, last by haegarr 15 years, 9 months ago
Hi all, I have a scene that has a bound: maxX, maxY, minx, minY, maxZ (may be = 0), minZ (may be = 0), the size of screen is: screenWidth, screenHeight. The point (0,0) is in the centre of the screen - I would like to centre this scene on the screen. - Zoom in and zoom out this scene but the scene is always in the centre of the screen. If the size of the scene is too large, it will be clipped. How can I do this? Thanks all,
Relax: http://video.igala.netAndroid Core http://androidcore.com
Advertisement
Position the camera where you like (with the exception of the center of the scene itself), and use gluLookAt or any function like that with the center of the scene
[ (maxX+minX)*0.5, (maxY+minY)*0.5, (maxZ+minZ)*0.5 ]
as the targeted point.
yeah, but when I do a zoom: glScalef(m_scale, m_scale, m_scale); the position of the scene is changed. Do I should multiply maxX, minX, maxY, minY, maxZ, minZ with m_scale and re-calcul the position of the center?
Relax: http://video.igala.netAndroid Core http://androidcore.com
Could you please show me where is the error in my code? I cannot see any thing on the screen with this code:


void Viewer::draw(){
glClearColor(0.f,0.f,0.1f,1.f);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glFrustumf(Loader->minX, Loader->maxX, Loader->minY, Loader->maxY, 3.f, 5000.f);
//glFrustumf(657, 1155, -781, -442, 1.2f, 1500.f);
glMatrixMode(GL_MODELVIEW);
glShadeModel(GL_SMOOTH);

glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
float centerX = (Loader->minX + Loader->maxX)/2;
float centerY = (Loader->minY + Loader->maxY)/2;
float centerZ = 0;
glTranslatef(centerX, centerY, centerZ);
//glTranslatef(-1400.f, 1000.f, -3.f);
//glScalef(m_scale, m_scale, m_scale);

glColor4f(0, 1, 0, 1);

drawScene();
}
Relax: http://video.igala.netAndroid Core http://androidcore.com
Answering your question isn't that easy because I don't know the purpose of the code. There is a glTranslate in your code, manipulating the VIEW portion of the MODELVIEW matrix. Is the purpose of that glTranslate to move the view, to move the camera, or to prepare the scaling?

In the 1st case it is presumbly incorrectly parametrized, in the 2nd case it is incorrectly located there, and in the 3rd case it is incomplete.

I assume (please correct me if I'm wrong) that you want to perform something like
V * Z * M
where V denotes the VIEW portion of the MODELVIEW matrix, Z the zoom, and M the MODEL portion of the MODELVIEW matrix. I.e. place the model in the world (M), scale the world (Z), and map the world to the view (V). This way is possible but requires a position correction
Z := T * S * T-1
so that the center-of-scaling (in your case the center of the scene's bbox) is translated to [ 0 0 0 ], then the scaling S is performed, and then the scene is translated back.

IMHO it would be better if the scaling is done in the view's local co-ordinate system
S * V * M
e.g. performed only on the local x and y axis. If you want you can shift that kind of zoom into the PROJECTION matrix (its a bit of question how fix a chosen zoom is).

Please clarify this before we continue ...
The purpose of glTranslate is to move my scene.


Now, my code that run well is :
void Viewer::initGLES(){	glViewport( 0, 0, iScreenWidth, iScreenHeight );	glClearColor(0.f,0.f,0.1f,1.f);	glDisable(GL_DEPTH_TEST);	glMatrixMode(GL_PROJECTION);	//glFrustumf (-100.f,1000.f,-1000.f,500.f,3.f,300.f);	glFrustumf(-1500.f, 1500.f, -1500.f, 1500.f, 3.f, 1500.f);    //glFrustumf(Loader->minX, Loader->maxX, Loader->minY, Loader->maxY, 3.f, 2500.f);    //glFrustumf(657, 1155, -781, -442, 1.2f, 1500.f);    glMatrixMode(GL_MODELVIEW);    glShadeModel(GL_SMOOTH);}//--------------------------------------------void Viewer::draw(){	glClear(GL_COLOR_BUFFER_BIT);	glLoadIdentity();	float centerX = (Loader->minX + Loader->maxX)/2;	float centerY = (Loader->minY + Loader->maxY)/2;	float centerZ = 0;	//glScalef(m_scale, m_scale, m_scale);	//glTranslatef(centerX, centerY, centerZ);glTranslatef(-1400.f, 1000.f, -3.f);	//glTranslatef(906.0f, -611.5f, -7.f);	//glScalef(2.0, 2.0, 2.0);		//glScalef(5.0, 5.0, 5.0);	glColor4f(0, 1, 0, 1);			drawScene();}


But as you can see, the parameters of the function glFrustumf(-1500.f, 1500.f, -1500.f, 1500.f, 3.f, 1500.f); are fixed. It is not good, because I don't know the real data, it must be read from the data file.

And in the function glTranslatef(-1400.f, 1000.f, -3.f);, the parameters are also fixed. I would like to center my scene.

//glFrustumf(657, 1155, -781, -442, 1.2f, 1500.f); --> The real value in the data file.


How should I change thes parameters?

Thanks,
Relax: http://video.igala.netAndroid Core http://androidcore.com
The position and orientation of the camera, the view frustum, and the zoom factor all play a role for which part of the scene will be visible. There are plenty of combinations that will work if you want an initial set-up where the entire scene is visible. So you need to think of some restrictions to allow an automatism to find a combination perfect for you. E.g. you should define the field-of-view angle. You should furthur define the initial viewing direction and distance. With these initial set-up, it should be possible to compute the frustum and initial camera location and initial zoom factor (without having checked it in detail yet).

The next thing you have to consider is that the VIEW portion of the MODELVIEW transformation is the inverse of the camera's local co-ordinate frame matrix. That means, with relation back to my previous post, the transformation
S * V == S * RC-1 * TC-1
where the index C denotes the camera, so that RC is its orientation matrix and TC its position matrix. With this in mind, the routines to be called look like
// glMatrixMode(GL_MODELVIEW);glLoadIdentity();glScalef( camera.zoom, camera.zoom, 1.0 );glMultMatrixf( camera.orientation.inverse );glTranslatef( -camera.position.x, -camera.position.y, -camera.position.z );
in order to set-up VIEW. Herein, the varous camera.XYZ thingies are to be computed as indicated above (we need to work on this issue if you accept or correct the above stuff).
I'm not good in computer graphic, so the OpenGL, especially the matrix are very difficult for me.

I think that the problem now is in two functions: glFrustumf and glTranslatef.

If I use this, it works (these values are groped/fumbled):



void Viewer::initGLES(){	glViewport( 0, 0, iScreenWidth, iScreenHeight );	glClearColor(0.f,0.f,0.1f,1.f);	glDisable(GL_DEPTH_TEST);	glMatrixMode(GL_PROJECTION);glFrustumf(-1500.f, 1500.f, -1500.f, 1500.f, 3.f, 1500.f);    glMatrixMode(GL_MODELVIEW);    glShadeModel(GL_SMOOTH);}//-----------------------------------void Viewer::draw(){	glClear(GL_COLOR_BUFFER_BIT);	glLoadIdentity();glTranslatef(-1400.f, 1000.f, -3.f);	glColor4f(0, 1, 0, 1);			drawScene();}






But it does not work with this one, I don’t know why:





void Viewer::initGLES(){	glViewport( 0, 0, iScreenWidth, iScreenHeight );	glClearColor(0.f,0.f,0.1f,1.f);	glDisable(GL_DEPTH_TEST);	glMatrixMode(GL_PROJECTION);    glFrustumf(Loader->minX, Loader->maxX, Loader->minY, Loader->maxY, 3.f, 1500.f);    //glFrustumf(657, 1155, -781, -442, 1.2f, 1500.f);    glMatrixMode(GL_MODELVIEW);    glShadeModel(GL_SMOOTH);}//------------------------void Viewer::draw(){	glClear(GL_COLOR_BUFFER_BIT);	glLoadIdentity();	float far = 1500;	float near = 1;	float centerX = (Loader->minX + Loader->maxX)/2;	float centerY = (Loader->minY + Loader->maxY)/2;	float centerZ = (far-near)/2;	glTranslatef(centerX, centerY, centerZ);		glColor4f(0, 1, 0, 1);			drawScene();}
Relax: http://video.igala.netAndroid Core http://androidcore.com
Sorry, but I can't write such posts without any matrix math, that is against my nature ;) Seriously, so many "why and why not"s are based in matrix math, and posting answers without some reasoning seems me wrong. So please forgive me. However, I hope that the one or other text between the formulas makes sense to you. So please read on.

Now to the problems. If you're looking at the description of the glFrustum routine,
Quote:man page of glFrustum
void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar )

PARAMETERS
left, right Specify the coordinates for the left and right vertical clipping planes.
bottom, top Specify the coordinates for the bottom and top horizontal clipping planes.
zNear, zFar Specify the distances to the near and far depth clipping planes. Both distances must be positive.
DESCRIPTION
glFrustum describes a perspective matrix that produces a perspective projection. [...]

you can see that the resulting matrix is multiplied normally to the current PROJECTION matrix. That is, its parameters are defined in the view's coordinate system. As a math expression (yes, I can't be without it ;)), it looks like
P * V * M
and since the perspective matrix P is on the left of V (you remember, its the view matrix), it is applied as written above.

But the bbox of your scene is presumbly given in world co-ordinate system. (It should so, or else some other stuff of your program would not work well.) In other words, it is very unlikely that using minX and friends directly and without any furthur computations in glFrustum is correct.

Going a step furthur, the left, right, top, and bottom parameters define the extent of a quadrangle that is located zNear units in front of the camera. The position of the camera and the corners of those quadrangle (to be understood as given relative to the camera) form a pyramid. Now think of a plane parallel to the quadrangle but in the distance zFar. And finally elongate the edges of the said pyramid until they hit the far plane. Then, the volume between the near quadrangle, the far plane, and the edges are what is called the view frustum.

Hence, for very most use cases, the correspondences right==-left and top==-bottom are used, or else the view frustum will be unsymmetrical. That is another hint that glFrustumf(Loader->minX, Loader->maxX, Loader->minY, Loader->maxY, 3.f, 1500.f) isn't what you want, since minX and maxX depend on the scene and can be anything, and maxX==-minX (for example) would be a fluke only.


What you presumbly want to do is: Define a field-of-view, e.g. 75 degree horinzontally. Choose this value w.r.t. the perspective distortion you want to tolerate. Then define a view distance as the value where parallel lines should not be foreshortened due to perspective projection. Let's assume you choose 100 length units for this. With a bit of trigonometry you find the correspondence
tan( alpha/2 ) = w / D
where alpha is the field-of-view (horizontally), w is the half width of the view, and D is the view distance. For a 4 by 3 view aspect ratio, you have the correspondence for the vertical dimension
h = 3/4 * w
where h is the half height of the view.

Now, the glFrustum wants the extents not at the view distance but at zNear. With help of the intercept theorems you get
w' := w * zNear / D
h' := h * zNear / D
and finally can call glFrustum with the parametrization:
glFrustum( -w', w', -h', h', zNear, zFar );

Okay so far? You don't want to change the frustum only to fit the scene into the view. What you want is to _place_ the frustum and to zoom the view so that the scene fits. One way would be as follows.

You know that at the chosen view distance D, the view has a width of 2*w and a height of 2*h, and you want to fit this with, say, the extent of the bbox in x dimension, i.e. maxX-minX, so you have to scale the view by a factor s1 so that
( maxX - minX ) * s1 == 2 * w
or, with some re-arrangement
s1 := 2 * w / ( maxX - minX )
If you want to have a bit of border, say for example 10%, then something like
s1 := 2 * w / ( 1.1 * ( maxX - minX ) )
instead would be appropriate.

Now that the bbox fits into the width, you have to ensure the same for the height:
s2 := 2 * h / ( maxY - minY ) // w/o a border
And finally choose the dominant scaling value:
s := min( s1, s2 )

Voila. Since the x and y dimensions are choson for fitting, you have to place the camera somewhere along the principal z axis. Moreover, you have to center the view in these both dimensions onto the bbox, so you have something like
posX := 0.5 * ( maxX + minX )
posY := 0.5 * ( maxY + minY )
posZ := D + max( minZ, maxZ )
Since, as already stated earlier, the view matrix is the inverse of the camera matrix, you have to use
glTranslatef( -posX, -posY, -posZ );

All together, under the assumption that I don't have missed something, I would say something like
glMatrixMode( GL_PERSPECTIVE);
glLoadIdentity();
glFrustum( -w', w', -h', h', zNear, zFar );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glScalef( s, s, 1 );
//glRotate( ... );
glTranslatef( -posX, -posY, -posZ );
should do the job. Notice please that the glRotate is commented out because we don't need a rotation of the standard camera since we've chosen x and y dimension to fit into the view.

Please bear in mind that I've written down all this from my head, so the one or other mistake may be included. However, the above should give you enough hints to understood how to solve the problem. But don't hesitate to ask if needed.


EDIT: Made corrections as far as described in a post below.

[Edited by - haegarr on July 22, 2008 10:29:47 AM]
The first thing I've seen to be corrected is posZ:
posZ := D + max( minZ, maxZ )
This is necessary to place the camera always outside the scene. The max seems me the correct choice since OpenGL uses a right handed co-ordinate system with a default camera looking along the negative z axis. I've corrected this also in my post above.

Notice please that all the math in the previous post works only if minX, maxX, minY, maxY, minZ, and maxZ are given in world co-ordinate system. I.e. the values define a so-called axis aligned bounding box (AABB).

This topic is closed to new replies.

Advertisement