3

I'm having a big problem limiting the camera pitch angle (between -90º and 90º) with the code below. This is somewhat a follow up to this question.

The problem, it seems, is that the camera rotates more than -90º or more than +90º and when that happens, I'll be looking down (or up) but but the view just rotated 180º around the Y axis.

Example: I'm facing north looking at the horizon and I start to look down until I can't go down anymore (limited by the code below). Then I start to look up and I'll be facing south.

void Camera::Rotate(Vector3D angle) {
    angle = angle * CAMERA_ROTATION_SPEED;

    accumPitchAngle += angle.x;

    if(accumPitchAngle > 90.0f) {
        angle.x = 90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = 90.0f;
    }

    if(accumPitchAngle < -90.0f) {
        angle.x = -90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = -90.0f;
    }

    // Rotate along the WORLD_SKY_VECTOR axis (yaw/heading rotation)
    // WORLD_SKY_VECTOR = (0.0f, 1.0f, 0.0f)
    if(angle.y != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Rotate along the x axis (pitch rotation)
    if(angle.x != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, RightVector, angle.x);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Makes sure all vectors are perpendicular all the time
    Reference.Normalize();
    RightVector = Vector3D::CrossProduct(Reference, UpVector);
    RightVector.Normalize();
    UpVector = Vector3D::CrossProduct(RightVector, Reference);
    UpVector.Normalize();
}

Vector3D Camera::RotateArbitraryAxis(const Vector3D v, Vector3D u, double angle) {
    Vector3D result;

    u.Normalize();

    double scalar = Vector3D::DotProduct(v, u);
    double c = cos(Math::DegreesToRadians(angle));
    double s = sin(Math::DegreesToRadians(angle));
    double a = 1.0f - c;

    result.x = u.x * scalar * a + (v.x * c) + ((-u.z * v.y) + (u.y * v.z)) * s;
    result.y = u.y * scalar * a + (v.y * c) + (( u.z * v.x) - (u.x * v.z)) * s;
    result.z = u.z * scalar * a + (v.z * c) + ((-u.y * v.x) + (u.x * v.y)) * s;

    return result;
}

The problem is probably in the if(angle.y != 0.0f) statement, if I comment that code block, the problem doesn't exist at all. It has something to do with WORLD_SKY_VECTOR, but that code is like that to allow me to rotate the heading and keep the camera leveled. If I used the UpVector instead, problem solved. But that is only good for a flight simulator, I need to keep the horizon leveled, that's the reason behind WORLD_SKY_VECTOR. But it seems that's the cause of "side switching" when I point the camera straight down.

As per requested on a comment below... This is for a first person (and third person but I haven't started implementing that part yet) camera and when I look straight down, -90º (or straight up, +90º) and when the angle goes from -89º to -91º (or from +89º to +91º) I want the camera to prevent that and don't go beyond the -90º, +90º limit. When it reaches that limit I need the camera to be able to go back (either up if I'm at -90º or down if I'm at +90º). Right now this only works sometimes, other times I'll be facing the other way instead of the way I was initially looking at.

Community
  • 1
  • 1
rfgamaral
  • 16,546
  • 57
  • 163
  • 275

3 Answers3

2

Problem 1 : The code belew "// Makes sure all vectors are perpendicular all the time" makes sure that your UP camera vector is actually up (with respect to WORLD_SKY_VECTOR). So :

  • When you look completely down, "up" does not mean a lot. In your case, your camera_up shoud be towards North, not thowards the sky
  • When you keep turning, past that point, the camera rotates to keep its up towarks the sky.

Look down at your chest. The top of your head is actually down. If you want to look at the same thing, from the same point, but with the top of your head up, you'd have to rotate it (not humanly feasible, sadly)

Problem 2 : Same as above. CrossProduct(Reference, WORLD_SKY_VECTOR) gives cross(up,down) which does not mean anything, just try on paper.

If you want to be able to look "upside down" :

  • Compute your view direction vector. You know reliably that because you have your orientation and your pitch.
  • Compute your right vector. It's the same, but with an orientation of 90° more, and a 0 pitch ( camera is always horizontal, i.e. you don't bend your head)
  • Compute your up vector as cross(right,front).

So :

yaw += whatever;
pitch += whatever;
FrontVector = SphericalToCartesian(yaw, pitch);
RightVector = SphericalToCartesian(yaw + PI/2.0f, 0.0);
UpVector = cross(RightVector, FrontVector);

with SphericalToCartesian beeing something like if (pitch=0,yaw=0) means looking to south:

x = cos(pitch) * sin(yaw)
y = sin(pitch)
z = cos(pitch) * cos(yaw)
Calvin1602
  • 9,413
  • 2
  • 44
  • 55
  • You're right on the "perpendicular code", I should be using `UpVector` and not `WORLD_SKY_VECTOR`, I changed that for other problem I was having and while I ended up fixing it, I screwed this one up. However, it still doesn't solve the problem. **I've updated the question why my full code for the `Rotate()` function, please take a look.** Also removed my second question which may not be relevant anymore, I'll post another question if I have to, edit your post to remove the answer to that second question if you want. – rfgamaral Mar 22 '11 at 18:44
  • **Note:** I did what you suggested, replaced `WORLD_SKY_VECTOR` with `UpVector` in the code below `// Makes sure all vectors are perpendicular all the time`. But I'm still using `WORLD_SKY_VECTOR` in the yaw/heading rotation to keep the camera always perpendicular to the ground/sky, otherwise it would feel like a flight simulator, I don't want that. I got this from the other question I linked on top of this post. – rfgamaral Mar 22 '11 at 18:46
  • No, the problem is not solved. The issue still exists. I just updated the question to hold the full updated code (I stripped a few lines before, thought they weren't important, but they might be). – rfgamaral Mar 22 '11 at 23:00
  • I think I understand why the problem is probably on `WORLD_SKY_VECTOR` as I'll have the `Reference` vector pointing straight down and the *sky* vector pointing straight up and that is problematic. I have no idea how to solve this other than using the solution by `aib` (limiting on -89.9 and +89.9 that is). – rfgamaral Mar 22 '11 at 23:09
  • The real question is : what do you want to happen when accumPitchAngle goes from 89° to 91° ? How do you want your camera to behave ? ( I'm not talking about code ). Is it a FPS camera ? A free-flight camera ? Describe it as much as you can (in your question if possible), the code depends on it. – Calvin1602 Mar 23 '11 at 12:04
  • I just come up with a simple and easy solution, posted as an answer, please take a look and let me know what you think. – rfgamaral Mar 23 '11 at 13:40
  • That's way more confusing and more code than my proposed solution. What's the problem with my solution? – rfgamaral Mar 23 '11 at 15:50
1

You can't take the cross product of two parallel vectors. I think that's where it's failing, i.e. when accumPitchAngle is ±90°.

You might want to limit it to -89.999° ~ +89.999°.

Edit: To start from the beginning, you want to convert pitch & yaw into a forward vector and an up vector for gluLookAt(), right? Then I suggest:

1) Use yaw (and yaw only) to create a right vector that is parallel to the ground.
2) Cross right and WORLD_SKY_VECTOR to get a forward vector that is correct in yaw but not in pitch.
3) Rotate forward around right through pitch degrees to get a forward vector that is correct in both pitch and yaw. (I think you've got this far.)
4) Cross forward and right to get a camera up vector that will work in all cases.

aib
  • 45,516
  • 10
  • 73
  • 79
  • I thought of that but I don't really like the idea, feels hacky and not a real solution. I have to deal with angles in another situations and doing that may cause other issues. For now, I'm trying to avoid that, if I can't find a better solution, I'll have to consider it. – rfgamaral Mar 22 '11 at 18:49
  • @Nazgulled: Instead of calculating `right` as a product of `forward` and `world_up`, can't you just calculate it from the yaw angle? If not, I'll suggest you to save both `forward` and `right` (or `forward` and `personal_up`) because a single vector is not enough information. – aib Mar 22 '11 at 19:25
  • In the edit you are suggesting that I scrap my current solution, in other words, scrap the solution (basically getting rid of `RotateArbitraryAxis()`) I was given on a different question which is working just fine besides that little issue. There must be another way to solve it than to rewrite everything. – rfgamaral Mar 22 '11 at 23:30
  • No, you will need that function to rotate `forward` around `right`. Note that the person who gave you that function suggested you use quaternions to get around this problem in the first place. Second, it is not a "solution" if it's not working and what makes you think the issue is "little" anyway? Fourth, you will never get anywhere if you're not willing to scrap code, especially one that's not working. (Case in point: You haven't realized how little you actually have to change.) – aib Mar 23 '11 at 00:03
  • But it is working. It's a function to rotate around an arbitrary axis and works just fine if I use an up vector instead of a sky vector. The problem is the vector, not the method. It's a little issue because I can just do what you initially suggested; the problem will be solved and no one will notice the difference. You're right, I have not realized what I have to change because I honestly found your answer confusing. In other words, I'm finding it hard to relate the names of your vectors with mine. I also think you're suggesting one more vector than what I have, which is confusing me further. – rfgamaral Mar 23 '11 at 00:18
  • About using quaternions and not willing to scrap code, that's untrue. I'm not here to create the perfect camera system using the best and most correct code to achieve it. I'm not here to develop the most awesome game ever. I'm not here to develop something good and sell it. I have to analyze everything and choose what's best considering my priorities. I don't have time to scrap everything nor time to learn quaternions and rewrite the whole thing. Am I willing to learn and do things better? Sure, that's where the fun's at. Do I have time? No. Changing to 89.9 is easy and effective. – rfgamaral Mar 23 '11 at 00:28
  • You know better than anyone by now that it's hard to explain this stuff without diagrams :). I'll have a go at Blender at work today if I have some time. Still, I'd wish you'd said you found my answer confusing. – aib Mar 23 '11 at 08:36
  • 1
    Anyway, the problem is that you're trying to create the `right` vector from the `forward` (reference) vector and the `WORLD_SKY_VECTOR`, which fails when they are parallel. You could more simply create it as a function of yaw: `right = (cos(yaw), 0.0, sin(yaw));` Then you could create a `forward_horizontal` vector by rotating `right` around `WORLD_SKY_VECTOR` using the function or simply `(cos(yaw-90), 0.0, sin(yaw-90))`. Then you could rotate it around `right` to give it pitch. Then you could cross it with `right` to get a "personal" `up` vector and use it with `gluLookAt()`. – aib Mar 23 '11 at 08:44
  • Yes, I've realized as much that that's the problem. I tried to implement your suggestion but I'm still confused. I don't know exactly what you mean by *yaw* (I tried different angle variables) and I have no idea where to put all that code and which code of my own to delete. Suffice to say, everything I've tried failed to work at all. Either way, I would rather use my `RotateArbitraryAxis` instead of all those cos/sin, wherever possible of course. – rfgamaral Mar 23 '11 at 12:59
  • I just come up with a simple and easy solution, posted as an answer, please take a look and let me know what you think. – rfgamaral Mar 23 '11 at 13:40
1

After while I come up with a solution which is pretty simple and straightforward. Like it was said many times in the answers and comments, the problem lies when the forward vector (my Reference) is looking straight up or down, which means this vector is parallel to WORLD_SKY_VECTOR and that's reason it gets all messy.

My thought behind this solution is that when I'm looking straight down (or up) and I want to rotate left or right, this is actually a roll rotation around the forward vector. Why not execute a roll movement when the pitch angle is at -90º or 90º instead?

Putting that together, I solved the problem by simply replacing the yaw/heading rotation with the following code:

if(angle.y != 0.0f) {
    if(abs(accumPitchAngle) == 90.0f) {
        RightVector = RotateArbitraryAxis(RightVector, Reference, -angle.y);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    } else {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }
}

This seems a fairly simple and straightforward working solution, I'll probably be marking my answer as accepted unless someone points out any strong problem against this implementation that I may not be considering. I'll wait a few hours before doing it, so that the previous answers reach some sort of conclusion.

rfgamaral
  • 16,546
  • 57
  • 163
  • 275
  • angle == 90.0f is hardly possible to happen. I edited my answer with actual code, could you please try it ? – Calvin1602 Mar 23 '11 at 14:03
  • What do you mean? It happens because of the clamping that happens before, if the angle is 88.7 for instance and the mouse is moved 3.2, `88.7 + 3.2 = 91.9` and so the value will be clamped to 90 and the pitch rotation will only be enough to reach 90 (`90 - (91.9 - 3.2) = 1.3`). This is where the problem lies. Unless I'm misunderstanding what you're saying. – rfgamaral Mar 23 '11 at 15:48