如何计算看点在OpenGL / GLUT中用鼠标移动相机? [英] How to calculate look at point to move the camera with the mouse in OpenGL/GLUT?

查看:634
本文介绍了如何计算看点在OpenGL / GLUT中用鼠标移动相机?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这会让我很困惑,所以请与我一起承担。



我已经在我的相机类中实现了大多数类型的动作和旋转,使用键盘,现在我想实现鼠标。我捕获鼠标移动像这样:

  #define SENSITIVITY 25.0f 

void main(void) {
(...)
glutPassiveMotionFunc(processPassiveMotion);
glutWarpPointer(WINDOW_WIDTH / 2,WINDOW_HEIGHT / 2);
glutSetCursor(GLUT_CURSOR_NONE);
(...)
}

void processPassiveMotion(int x,int y){
int centerX = WINDOW_WIDTH / 2;
int centerY = WINDOW_HEIGHT / 2;

int deltaX = -1 *(x - centerX);
int deltaY = -1 *(y - centerY);

if(deltaX!= 0 || deltaY!= 0){
mainCamera.Rotate(deltaX / SENSITIVITY,deltaY / SENSITIVITY);

glutWarpPointer(centerX,centerY);
}
}

我读完了一切后,足够在我的情况。但是我必须声明,首先我尝试调用 Pitch() Yaw()没有去,我不得不创建一个额外的功能旋转两个轴在同一时间。



旋转功能类似这样:

  #define DEG2RAD(a)(a *(M_PI / 180.0f))
#define SINDEG )
#define COSDEG(a)cos(DEG2RAD(a))

void Camera :: Rotate(GLfloat angleX,GLfloat angleY){
Reference = NormalizeVector b参考* COSDEG(angleY)+ UpVector * SINDEG(angleY)
);

参考= NormalizeVector(
参考* COSDEG(angleX) - RightVector * SINDEG(angleX)
);

UpVector = CrossProduct(& Reference,& RightVector)*(-1);
RightVector = CrossProduct(& Reference,& UpVector);
}

参考观看方向,相机正在观看的点。因为它是一个标准化的向量,它从-1.0到1.0。这个向量或点稍后与另一个向量( Position ,它是相机位置)一起使用,以计算在 gluLookAt ,像这样:

  void Camera :: LookAt(void){
Vector3D viewPoint = Position + Reference;

gluLookAt(
Position.x,Position.y,Position.z,
viewPoint.x,viewPoint.y,viewPoint.z,
UpVector.x, UpVector.y,UpVector.z
);
}

上述所有向量操作类似 + - * 当然是重载的。



现在我要尝试描述我的问题...



上面的旋转函数在正确执行俯仰和偏航通过使用鼠标。但是,这些旋转看起来不像第一人称射击游戏。在那些游戏中,当一个人看着天空,然后看左/右,一个人期望看着天空。想象我们在一个球体内,这样的运动应该在球体的顶部画一个圆。



但这不是发生,因为这不是一个偏航。偏航运动将围绕任意轴旋转,我认为这是向上的向量。所以,问题是在偏航运动,因为音调似乎工作正常。



换句话说,我的代码不能保持水平线,这是什么必须发生的原因,这是发生在游戏中,当一个人看着天空,然后看左/右,地平线总是水平。同样的情况也不会发生在我的代码,我抬头,然后左/右,地平线将扭曲。



我使自己足够清楚了吗?我不知道我该如何解释这更好。



我不知道该如何解决这个问题...如何在查找后正确地左/右查看/ down,保持水平线水平?



编辑:



代码是从Yaw和Pitch函数中获得的,它们也存在,所以我可以独立地调用这些旋转。为了参考,我将在下面添加它们Roll函数(我可能永远不会使用,但是如果我需要它,它在那里):

  void Camera :: Pitch(GLfloat angle){
Reference = NormalizeVector $ b引用* COSDEG(angle)+ UpVector * SINDEG(angle)
);

UpVector = CrossProduct(& Reference,& RightVector)*(-1);


void Camera :: Yaw(GLfloat angle){
参考= NormalizeVector(
参考* COSDEG(角度) - RightVector * SINDEG(角度)
) ;

RightVector = CrossProduct(& Reference,& UpVector);
}

void Camera :: Roll(GLfloat angle){
RightVector = NormalizeVector(
RightVector * COSDEG(angle) - UpVector * SINDEG $ b);

UpVector = CrossProduct(& Reference,& RightVector)*(-1);
}


解决方案

  UpVector = CrossProduct(& Reference,& RightVector)*(-1); 

在上一条语句中将Reference引用向RightVector后,一个UpVector,给你一个水平地平线。用你的手臂试试。此外,Reference和RightVector不分隔90度,因此UpVector甚至不会是单位向量。 (最后,你应该真的只是为了清晰而切换交叉乘积的顺序,而不是乘以(-1)。)



老实说,如果我这样做,我会采取不同的方法。我没有看到任何逻辑的原因,为什么两个旋转必须在一个函数。当使用向量时,我也不惜一切代价避免明确的正弦和余弦。我认为您真正需要的是旋转任意轴的功能。如果没有别的,它非常有用。幸运的是,所有的细节都由Murray先生照顾!如果你实现这个功能,那么它变得很简单。定义一个始终指向上方的常量 SkyVector 。然后在伪代码中,

  AxisRotation(Vector vec,Vector axis,float angle){
Vector result;

//轴被假定为规范化的:
//(只是确保你没有修改原来的)
axis = NormalizeVector(& axis);

//为了清楚起见,扩展:
float u = axis.x;
float v = axis.y;
float w = axis.z;
float x = vec.x;
float y = vec.y;
float z = vec.z;
float c = cos(angle);
float s = sin(angle);

//从链接页面中逐字应用公式:
result.x = u *(u * x + v * y + w * z)*(1.-c)+ x * c +(-w * y + v * z)* s;
result.y = v *(u * x + v * y + w * z)*(1.-c)+ y * c +(w * x- u * z)* s;
result.z = w *(u * x + v * y + w * z)*(1.-c)+ z * c +(-v * x + u * y)

返回结果;
}

Yaw(angleX){
Reference = AxisRotation(& Reference,& SkyVector,angleX);
RightVector = NormalizeVector(CrossProduct(& Reference,& SkyVector));
UpVector = CrossProduct(& RightVector,& Reference);
}

Pitch(angleY){
Reference = AxisRotation(& Reference,& RightVector,angleY);
// RightVector不会改变!
UpVector = CrossProduct(& RightVector,& Reference);
}

如果你通过操作来完成这个操作,它应该是有意义的。最后,我要补充一点,四元数是真正的正确方式来做这件事,避免万向锁,但我通常做的事情,你做了什么。你可能需要检查每一个,然后确保你的矢量保持漂亮和垂直。

编辑:如果轴旋转功能是过度的,您仍然可以使用简单的向量和旋转矩阵来实现此功能。唯一的事情是你必须开始投射东西到水平面,这样你可以独立做两个旋转,它仍然会采取一些正弦和余弦。你的时间可能更好地用于实现轴旋转功能!


This will be confusing for me to explain so please bear with me.

I've already implemented most type of movements and rotations in my camera class, everything is working with the keyboard, now I want to implement the mouse. I capture the mouse movement like this:

#define SENSITIVITY 25.0f

void main(void) {
    (...)
    glutPassiveMotionFunc(processPassiveMotion);    
    glutWarpPointer(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2);
    glutSetCursor(GLUT_CURSOR_NONE);
    (...)
}

void processPassiveMotion(int x, int y) {
    int centerX = WINDOW_WIDTH / 2;
    int centerY = WINDOW_HEIGHT / 2;

    int deltaX = -1 * (x - centerX);
    int deltaY = -1 * (y - centerY);

    if(deltaX != 0 || deltaY != 0) {
        mainCamera.Rotate(deltaX / SENSITIVITY, deltaY / SENSITIVITY);

        glutWarpPointer(centerX, centerY);
    }
}

After everything I've read, I believe this is enough in my situation. However I must state that first I tried to call the Pitch() and Yaw() camera functions but it was a no go, I had to create an extra function to rotate both axis "at the same time".

That rotate function goes something like this:

#define DEG2RAD(a) (a * (M_PI / 180.0f))
#define SINDEG(a)  sin(DEG2RAD(a))
#define COSDEG(a)  cos(DEG2RAD(a))

void Camera::Rotate(GLfloat angleX, GLfloat angleY) {
    Reference = NormalizeVector(
        Reference * COSDEG(angleY) + UpVector * SINDEG(angleY)
    );

    Reference = NormalizeVector(
        Reference * COSDEG(angleX) - RightVector * SINDEG(angleX)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
    RightVector = CrossProduct(&Reference, &UpVector);
}

The Reference is the viewing direction, the point the camera is looking at. And since it's a normalized vector, it goes from -1.0 to 1.0. This vector, or point, is later used together with another vector (Position, which is the camera location) to calculate the real look at point to use in gluLookAt, like this:

void Camera::LookAt(void) {
    Vector3D viewPoint = Position + Reference;

    gluLookAt(
        Position.x, Position.y, Position.z,
        viewPoint.x, viewPoint.y, viewPoint.z,
        UpVector.x, UpVector.y, UpVector.z
    );
}

All vector operations above like +, - and * are overloaded of course.

Now I'm going to try to describe my problem...

The rotate function above works just fine in the sense that it correctly performs a pitch and yaw by using the mouse. However, those rotations don't look like the ones in First Person Shooter games. In those games, when one looks at sky and then looks left/right, one expects to keep looking at the sky. Imagining we are inside a sphere, a movement like that should "draw" a circle in the top part of the sphere.

But that's not what happens because that's not what a yaw does. A yaw movement will rotate around an arbitrary axis, which I think is the up vector in this situation. So, the problem is in the yaw movement because the pitch seems to work fine.

In other words, my code above can't keep the horizon leveled and that's what must happen cause that's happens in games when one looks at the sky and then look left/right, the horizon is always leveled. The same will not happen with my code, I look up and then left/right, and the horizon will be all twisted.

Did I make myself clear enough? I'm not sure how can I explain this any better. :( Hopefully it's enough for anyone to understand.

I'm not sure how can I fix this problem... How can I look left/right correctly after looking up/down, keeping the horizon leveled?

EDIT:

My rotate function code is taken from both the Yaw and Pitch functions which also exist so I can call those rotations independently. For reference purposes I'll add them below along with the Roll function too (which I'll probably never use, but in case I need it, it's there):

void Camera::Pitch(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) + UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

void Camera::Yaw(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) - RightVector * SINDEG(angle)
    );

    RightVector = CrossProduct(&Reference, &UpVector);
}

void Camera::Roll(GLfloat angle) {
    RightVector = NormalizeVector(
        RightVector * COSDEG(angle) - UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

解决方案

Your problem appears to be in the statement:

UpVector = CrossProduct(&Reference, &RightVector) * (-1);

Once you've rotated Reference toward RightVector in the previous statement, their cross product will no longer result in an UpVector that gives you a horizontal horizon. Try it with your arms. Furthermore, Reference and RightVector are not separated by 90 degrees, so UpVector won't even be a unit vector either. (Finally, you should really just switch the order of the cross product for clarity, rather than multiplying by (-1).)

Honestly, if I were doing it, I would take a different approach. I don't see any logical reason why the two rotations have to be in one function. I also avoid explicit sines and cosines at all costs when working with vectors. I think what you really need is a function to Rotate About an Arbitrary Axis. If nothing else, it's very useful. Fortunately all the details are taken care of by Mr. Murray! If you implement this function, then it becomes very simple. Define a constant SkyVector that always points upward. Then in pseudocode,

AxisRotation( Vector vec, Vector axis, float angle ) {
    Vector result;

    // The axis is assumed to be normalized:  
    //    (just make sure you're not modifying the original)
    axis = NormalizeVector( &axis );

    // expanded for clarity:
    float u = axis.x;
    float v = axis.y;
    float w = axis.z;
    float x = vec.x;
    float y = vec.y;
    float z = vec.z;
    float c = cos(angle);
    float s = sin(angle);

    // Apply the formula verbatim from the linked page:
    result.x = u*(u*x + v*y + w*z)*(1.-c) + x*c + (-w*y + v*z)*s;
    result.y = v*(u*x + v*y + w*z)*(1.-c) + y*c + ( w*x - u*z)*s;
    result.z = w*(u*x + v*y + w*z)*(1.-c) + z*c + (-v*x + u*y)*s;

    return result;
}

Yaw(angleX) {
    Reference = AxisRotation( &Reference, &SkyVector, angleX );
    RightVector = NormalizeVector( CrossProduct( &Reference, &SkyVector ) );
    UpVector = CrossProduct( &RightVector, &Reference );
}

Pitch(angleY) {
    Reference = AxisRotation( &Reference, &RightVector, angleY );
    //RightVector doesn't change!
    UpVector = CrossProduct( &RightVector, &Reference );
}

If you go through that operation by operation, it should hopefully make some sense. Finally, I'll add that quaternions are really the 'correct' way to do this stuff and avoid gimbal lock, but I usually do pretty much exactly what you've done. You might have to check every now and then to make sure your vectors stay nice and perpendicular. Quaternions are more stable.

Edit: If the axis rotation function is overkill, you can still implement this with simple vectors and rotation matrices. The only thing is you'll have to start projecting things into the horizontal plane so that you can do the two rotations independently And it'll still take some sines and cosines. Your time is probably better spent implementing the axis rotation function!

这篇关于如何计算看点在OpenGL / GLUT中用鼠标移动相机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆