Zooming into the mouse, factoring in a camera translation? (OpenGL) - c++

Here is my issue, I have a scale point, which is the unprojected mouse position. I also have a "camera which basically translates all objects by X and Y. What I want to do is achieve zooming into mouse position.
I'v tried this:
1. Find the mouse's x and y coordinates
2. Translate by (x,y,0) to put the origin at those coordinates
3. Scale by your desired vector (i,j,k)
4. Translate by (-x,-y,0) to put the origin back at the top left
But this doesn't factor in a translation for the camera.
How can I properly do this. Thanks
glTranslatef(controls.MainGlFrame.GetCameraX(),
controls.MainGlFrame.GetCameraY(),0);
glTranslatef(current.ScalePoint.x,current.ScalePoint.y,0);
glScalef(current.ScaleFactor,current.ScaleFactor,0);
glTranslatef(-current.ScalePoint.x,-current.ScalePoint.y,0);

Instead of using glTranslate to move all the objects, you should try glOrtho. It takes as parameters the wanted left coords, right coords, bottom coords, top coords, and min/max depth.
For example if you call glOrtho(-5, 5, -2, 2, ...); your screen will show all the points whose coords are inside a rectangle going from (-5,2) to (5,-2). The advantage is that you can easily adjust the zoom level.
If you don't multiply by any view/projection matrix (which I assume is the case), the default screen coords range from (-1,1) to (1,-1).
But in your project it can be very useful to control the camera. Call this before you draw any object instead of your glTranslate:
float left = cameraX - zoomLevel * 2;
float right = cameraX + zoomLevel * 2;
float top = cameraY + zoomLevel * 2;
float bottom = cameraY - zoomLevel * 2;
glOrtho(left, right, bottom, top, -1.f, 1.f);
Note that cameraX and cameraY now represent the center of the screen.
Now when you zoom on a point, you simply have to do something like this:
cameraX += (cameraX - screenX) * 0.5f;
cameraY += (cameraY - screenY) * 0.5f;
zoomLevel += 0.5f;

Related

OpenGL: mouse move objects, object can't follow mouse

After picking an object with the mouse, I want to be able to move the object using the mouse. First, I translate mouse position to world position, and use glReadPixels() to read the depth of the object as z's:
double xpos, ypos, zpos;
glfwGetCursorPos(window_ptr, &xpos, &ypos);
float xPercent = (xpos + 0.5f) / scr_width_ * 2.0f - 1; // range is -1 to +1
float yPercent = (ypos + 0.5f) / scr_height_ * 2.0f - 1; // range is -1 to +1
yPercent = -yPercent;
glReadPixels(xpos, scr_height_ - ypos - 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zPercent);
then we move the mouse until we want to release the mouse.
last_position = (xPercent, yPercent, zPercent);
Finally we use the same value as z's value and calculate the x's world position and y's position:
current_position = (xPercent, yPercent, zPercent);
then we translate the object model:
model = glm::translate(model, current_position - last_postion);
the issue is:
the object's speed is not same as mouse's.
waiting for your answer.
The problem is caused by the difference in coordinate systems between the screen and the world. Your cursor position is in "percent" (fraction of the screen width), but your objects are likely placed in some other coordinate system that is determined by your projection matrix (for example, in meters). Unless you are using an orthographic projection and the world-space coordinates of the object are in the same space as the screen-space coordinates, you will get different motion.
For example, even if you had an orthographic projection, it may be configured such that the screen maps to a region of game world that is 20 meters wide, so moving your mouse anywhere within the range [-1, 1] (across the full width of the screen) will only translate to the object moving over 1/10th of the screen.
Furthermore, you may be working with a perspective projection. In that case, not only will your world coordinates differ from the screen coordinates, but there is actually a nonlinear transformation between the screen coordinates and the world coordinates that will cause the object's motion to be distorted near the edges of the screen. You can correct for this by un-projecting your mouse cursor's screen-space coordinates back into world coordinates using glm::unproject. Here is a good explanation of this process.

OpenGL screen-to-world coordinates conversion

So the default 2d clipping area of opengl is left -1.0 to right 1.0, and buttom -1.0 to top 1.0
And the window I created for an opengl program is 640 pixles in width and 480 pixels in height. The top left pixel is (0,0), the button right pixel is (640, 480)
I also wrote a function to retrive the coordinates when I click and drag and release the mouse button(When I click, it's (x1,y1) and when I release it's(x2,y2) )
So what should I do to convert (x1,y1) and (x2,y2) to the corresponding position in the clipping area?
The answer given by #BDL might get you close enough for what you need, but the calculations are not really correct.
The division needs to be by the number of pixels in each coordinate direction, because you do have 640/480 pixels within the coordinate range.
One subtle detail to take into account is that, when you get a given position from your mouse input, these will be the integer coordinates of the pixels. If you simply apply the scaling based on the window size, the resulting OpenGL coordinate would map to the left/bottom edge of the pixel. But what you most likely want is the center of the pixel. To precisely transform this into the OpenGL coordinate space, you can simply apply a 0.5 offset to your input value, moving the value from the edge to the center of the pixel.
For example, the left most pixel would have x-coordinate 0, the right most 639. The centers of these two, after applying the 0.5 offset, are 0.5 and 639.5. Applying this correction, you can also see that they are now both a distance of 0.5 away from the corresponding edges of the area at 0 and 640, making the whole thing symmetrical.
So the correct calculation is:
float xClip = ((xPix + 0.5f) / 640.0f) * 2.0f - 1.0f;
float yClip = 1.0f - ((yPix + 0.5f) / 480.0f) * 2.0f;
Or slightly simplified:
float xClip = (xPix + 0.5f) / 320.0f - 1.0f;
float yClip = 1.0f - (yPix + 0.5f) / 240.0f;
This takes the y-inversion into account.
I assume that the rightmost pixel is 639 (otherwise your window would be 641 pixels large).
The transformation is quiet simple, we just need a linear mapping. To transform a point P from pixel coordinates to clipping coordinates one can use the following formula
319.5
P_clip = (P_pixel / [ ]) - 1.0
239.5
Let's go over it step by step for the x coordinate. First we transform the [0, 639] range to a [0, 1] range by dividing through the window width
P_01 = P_pixel_x / 639
Then we transform from [0, 1] to [-1, 1] by multiplying by 2 and subtracting 1
P_clip_x = P_01 * 2 - 1
When one combines these two calculations and extends it to the y coordinate one gets the equation given above.

opengl: avoid clipping in perspective mode?

It looks like the solution is to change projection matrix on-the-fly? Let me do some research to see how to do it correctly.
My scenario is:===>
Say, now, I created a 3D box in a window under windows7 with perspective mode enabled. From users point of view, when users move(rotate/translate) this box, when the box is out of the window, it should be clipped/(hidden partly), that's correct. But when the box is moved inside the window, the box should always be shown totally (not clipped!), right? But my problem is, sometime, when users move the box inside the window, he would see some parts of this box are clipped (for example, one vertex of this box is clipped away). There is no limit how much users can move this box.
My understanding is:===>
when users move the box, this box is out of frustum, that's why it's clipped.
In this case, my code should adjust the frustum on-the-fly (then, projection mattrix is changed) or adjust camera on-the-fly (maybe, adjust the near-far plane as well) or do something else?
My question is:===>
what's the popular technique to avoid this kind of clipping? And make sure users feel they are moving box smoothly, not having any "jerk" (like, suddenly, the box's location is jumped to another location (because our frustum is suddenly changed largely) when users are moving the box ).
I think this is a very classic problem, there should be a perfect solution. Any code/references are appreciated!
I attached a picture to show the problem:
This was happening to me , and adjusting the perspective matrix did not allow a near plane below .5 without all my objects disappearing.
Then I read this somewhere:
DEPTH CLAMPING. - The clipping behavior against the Z position of a vertex
( ie: -w_c \ le z_c \ le w_c ) can be turned off by activating depth clamping.
glEnable( GL_DEPTH_CLAMP ) ;
And I could get close to my objects without them being clipped away.
I do not know if doing this will cause other problems , but as of yet I have not encountered any.
I would suspect that your frustum is too narrow. So, when you rotate your object parts of it are moving outside of the viewable area. As an experiment, try increasing your frustum angle, increasing your Far value to something like 1000 or even 10000 and move your camera further back from centre (higher negative value on the z-plane). This should generate a very large frustum that your object should fit within. Run your project and rotate - if the clipping effect is gone you know your problem is either with the frustum or the model scale (or both).
This code gets called before every redraw. I don't know how you're rotating/translating (timer or mouseDown), but in any case the methods described below can be done smoothly and appear natural to the user.
If your object is being clipped by the near plane, move the near cutoff plane back toward the camera (in this code, increase VIEWPLANEOFFSET). If the camera is too close to allow you to move the near plane far enough back, you may also need to move the camera back.
If your object is being clipped by the left, right, top or bottom clipping planes, adjust the camera aperture.
This is discussed in more detail below.
// ******************************* Distance of The Camera from the Origin
cameraRadius = sqrtf((camera.viewPos.x * camera.viewPos.x) + (camera.viewPos.y * camera.viewPos.y) + (camera.viewPos.z * camera.viewPos.z));
GLfloat phi = atanf(camera.viewPos.x/cameraRadius);
GLfloat theta = atanf(camera.viewPos.y/cameraRadius);
camera.viewUp.x = cosf(theta) * sinf(phi);
camera.viewUp.y = cosf(theta);
camera.viewUp.z = sinf(theta) * sinf(phi);
You'll see with the View matrix we're only defining the camera (eye) position and view direction. There's no clipping going on here yet, but the camera position will limit what we can see in that if it's too close to the object, we'll be limited in how we can set the near cutoff plane. I can't think of any reason not to set the camera back fairly far.
// ********************************************** Make the View Matrix
viewMatrix = GLKMatrix4MakeLookAt(camera.viewPos.x, camera.viewPos.y, camera.viewPos.z, camera.viewPos.x + camera.viewDir.x, camera.viewPos.y + camera.viewDir.y, camera.viewPos.z + camera.viewDir.z, camera.viewUp.x, camera.viewUp.y, camera.viewUp.z);
The Projection matrix is where the clipping frustum is defined. Again, if the camera is too close, we won't be able to set the near cutoff plane to avoid clipping the object if it's bigger than our camera distance from the origin. While I can't see any reason not to set the camera back fairly far, there are reasons (accuracy of depth culling) not to set the near/far clipping planes any further apart than you need.
In this code the camera aperture is used directly, but if you're using something like glFrustum to create the Projection matrix, it's a good idea to calculate the left and right clipping planes from the camera aperture. This way you can create a zoom effect by varying the camera aperture (maybe in a mouseDown method) so the user can zoom in or out as he likes. Increasing the aperture effectively zooms out. Decreasing the aperture effectively zooms in.
// ********************************************** Make Projection Matrix
GLfloat aspectRatio;
GLfloat cameraNear, cameraFar;
// The Camera Near and Far Cutoff Planes
cameraNear = cameraRadius - VIEWPLANEOFFSET;
if (cameraNear < 0.00001)
cameraNear = 0.00001;
cameraFar = cameraRadius + VIEWPLANEOFFSET;
if (cameraFar < 1.0)
cameraFar = 1.0;
// Get The Current Frame
NSRect viewRect = [self frame];
camera.viewWidth = viewRect.size.width;
camera.viewHeight = viewRect.size.height;
// Calculate the Ratio of The View Width / View Height
aspectRatio = viewRect.size.width / viewRect.size.height;
float fieldOfView = GLKMathDegreesToRadians(camera.aperture);
projectionMatrix = GLKMatrix4MakePerspective(fieldOfView, aspectRatio, cameraNear, cameraFar);
EDIT:
Here is some code illustrating how to calculate left and right clipping planes from the camera aperture:
GLfloat ratio, apertureHalfAngle, width;
GLfloat cameraLeft, cameraRight, cameraTop, cameraBottom, cameraNear, cameraFar;
GLfloat shapeSize = 3.0;
GLfloat cameraRadius;
// Distance of The Camera from the Origin
cameraRadius = sqrtf((camera.viewPos.x * camera.viewPos.x) + (camera.viewPos.y * camera.viewPos.y) + (camera.viewPos.z * camera.viewPos.z));
// The Camera Near and Far Cutoff Planes
cameraNear = cameraRadius - (shapeSize * 0.5);
if (cameraNear < 0.00001)
cameraNear = 0.00001;
cameraFar = cameraRadius + (shapeSize * 0.5);
if (cameraFar < 1.0)
cameraFar = 1.0;
// Calculte the camera Aperture Half Angle (radians) from the Camera Aperture (degrees)
apertureHalfAngle = (camera.aperture / 2) * PI / 180.0; // half aperture degrees to radians
// Calculate the Width from 0 of the Left and Right Camera Cutoffs
// We Use Camera Radius Rather Than Camera Near For Our Own Reasons
width = cameraRadius * tanf(apertureHalfAngle);
NSRect viewRect = [self bounds];
camera.viewWidth = viewRect.size.width;
camera.viewHeight = viewRect.size.height;
// Calculate the Ratio of The View Width / View Height
ratio = camera.viewWidth / camera.viewHeight;
// Calculate the Camera Left, Right, Top and Bottom
if (ratio >= 1.0)
{
cameraLeft = -ratio * width;
cameraRight = ratio * width;
cameraTop = width;
cameraBottom = -width;
} else {
cameraLeft = -width;
cameraRight = width;
cameraTop = width / ratio;
cameraBottom = -width / ratio;
}

gluLookAt eyeZ not working as excpected

I'm having some trouble with the eyeZ value of gluLookAt.
The way I'd imagine it to work is like moving a camera further away, thus shrinking the object in your field of view.
I have a simple setup with a simple shape in 3d space draw via glDrawElements with an 100x100x100 ortho where 0, 0, 0 is the center of the universe. The object is at 0, 0, 0.
I'm trying to make it so when you scroll the mouse wheel you get further away/closer to the object. Here's how glulookat is called.
float eyeX = 0;
float eyeY = 0;
float eyeZ = differenceInMouseWheel();
float centerX = 0;
float centerY = 0;
float centerZ = 0;
float upX = 0;
float upY = 1;
float upZ = 0;
gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
The only thing changing here is eyeZ.
The effect is strange, I scroll for about 10 seconds and then suddenly half of the object disappears. From there more and more of it disappears. This is probably because the camera is going out off into the 50 z distance limit, but I can't understand why the object doesn't scale like it would in 3D space.
Maybe I'm misunderstanding how the center values work?
I've also tried applying differenceInMouseWheel() to centerZ but that changed nothing, I'm going to assume the center values are just so glu can get a direction and nothing more.
Maybe the up vector should change? I don't know at this point.
You are using an orthographic projection. This means that no matter how great the distance, your objects will always appear to have the same size. Your object will disappear once it reaches the far clipping plane however, which is what you are seeing when you scroll for a long time.
You have two options: Either you use a perspective projection or you implement a zoom by modifying the orthographic projection matrix like so:
Let zoom be in (0, 1], and let viewport be a rectangle that is set to your current viewport. Let near be your near clipping plane distance and far be your far clipping plane distance.
glOrtho(zoom * viewport.width / 2, zoom * viewport.width / 2, zoom * viewport.height / 2, zoom * viewport.height / 2, near, far);
Are you using a perspective projection matrix, or an orthographic one? If you don't use a perspective matrix the object's wont appear to change in size as you move the camera around.

Transforming verticies with center point and scale factor?

My application is a vector drawing application. It works with OpenGL. I will be modifying it to instead use the Cairo 2D graphics library. The issue is with zooming. With openGL camera and scale factor sort of work like this:
float scalediv = Current_Scene().camera.ScaleFactor / 2.0f;
float cameraX = GetCameraX();
float cameraY = GetCameraY();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float left = cameraX - ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float right = cameraX + ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float bottom = cameraY - ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
float top = cameraY + ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
glOrtho(left,
right,
bottom,
top,
-0.01f,0.01f);
// Set the model matrix as the current matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
hdc = BeginPaint(controls.MainGlContext.mhWnd,&ps);
Mouse position is obtained like this:
POINT _mouse = controls.MainGlFrame.GetMousePos();
vector2f mouse = functions.ScreenToWorld(_mouse.x,_mouse.y,GetCameraX(),GetCameraY(),
Current_Scene().camera.ScaleFactor,
controls.MainGlFrame.Dimensions.x,
controls.MainGlFrame.Dimensions.y );
vector2f CGlEngineFunctions::ScreenToWorld(int x, int y, float camx, float camy, float scale, int width, int height)
{
// Move the given point to the origin, multiply by the zoom factor and
// add the model coordinates of the center point (camera position)
vector2f p;
p.x = (float)(x - width / 2.0f) * scale +
camx;
p.y = -(float)(y - height / 2.0f) * scale +
camy;
return p;
}
From there I draw the VBO's of triangles. This allows me to pan and zoom in. Given that Cairo only can draw based on coordinates, how can I make it so that a vertex is properly scaled and panned without using transformations. Basically GlOrtho sets the viewport usually but I dont think I could do this with Cairo.
Well GlOrtho is able to change the viewport matrix instead of modifying the verticies but how could I instead modify the verticies to get the same result?
Thanks
*Given vertex P, which was obtained from ScreenToWorld, how could I modify it so that it is scaled and panned accordng to the camera and scale factor? Because usually OpenGL would essentially do this
I think Cairo can do what you want ... see http://cairographics.org/matrix_transform/ . Does that solve your problem, and if not, why ?