Problems with my OpenGL Camera movement - c++

I seem to be having problems with my OpenGl camera. When I first created it, everything worked fine. However since importing models and creating objects, I've noticed weird things happening.
Firstly, heres my code for movement:
float xRot = (Pitch / 180 * 3.141592654f),
yRot = (Yaw / 180 * 3.141592654f);
float sinX = float(sin(xRot)) * myInput.Sensitivity,
sinY = float(sin(yRot)) * myInput.Sensitivity,
cosY = float(cos(yRot)) * myInput.Sensitivity;
if(myInput.Keys['W']) //Forwards
{
curPos.x += sinY;
curPos.z -= cosY;
curPos.y += sinX;
}
else if(myInput.Keys['S']) //Backwards
{
curPos.x -= sinY;
curPos.z += cosY;
curPos.y -= sinX;
}
if(myInput.Keys['A']) //Left
{
curPos.x -= cosY;
curPos.z -= sinY;
}
else if(myInput.Keys['D']) //Right
{
curPos.x += cosY;
curPos.z += sinY;
}
//Move camera up and down
if(myInput.Keys['Q'])
curPos.y-= myInput.Sensitivity; //up
else if(myInput.Keys['E'])
curPos.y+= myInput.Sensitivity; //down
//Check col
if(curPos.y < 0)
curPos.y = 0;
else if(curPos.y >= 300) //Gimbal lock encountered
curPos.y = 299;
Secondly, heres my movement for calculating gluLookAt:
double cosR, cosP, cosY; //temp values for sin/cos from
double sinR, sinP, sinY; //the inputed roll/pitch/yaw
cosY = cosf(Yaw*3.1415/180);
cosP = cosf(Pitch*3.1415/180);
cosR = cosf(Roll*3.1415/180);
sinY = sinf(Yaw*3.1415/180);
sinP = sinf(Pitch*3.1415/180);
sinR = sinf(Roll*3.1415/180);
//forward position
forwardPos.x = sinY * cosP*360;
forwardPos.y = sinP * 360;
forwardPos.z = cosP * -cosY*360;
//up position
upPos.x = -cosY * sinR - sinY * sinP * cosR;
upPos.y = cosP * cosR;
upPos.z = -sinY * sinR - sinP * cosR * -cosY;
Basically I've noticed that if the camera goes above 300 in the Y-Axis then the view starts rotating, the same thing happens if I move too far in the x/z axis.
Can anyone see whats wrong with my code? Am I working in deg when I should be working in rad?

I haven't checked your code but this type of issue is notorious when using Euler angles for 3D rotations.
It's called gimbal lock (I see you have a comment in your code that shows you're aware of it) and the easiest solution to overcoming it is to switch to using quaternions to calculate your rotation matrices.
There's a really good OpenGL article on the Wiki here.

Related

How to scale the rotation of a quaternion

I am trying to do the equivalent of multiplying the velocity by the time between frames. I would imagine that doing this for quaternions would be done by raising them to a power. I have code to rotate an object based on my mouse movements. It has a main loop running at one frame rate and a physics loop running at a fixed frame rate. Here is the relevant part of the main loop:
glfwPollEvents();
Input::update();
window.clear(0,0,0,1);
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().x, glm::vec3(0,1,0));
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().y, glm::vec3(1,0,0));
if(Input::getKey(Input::KEY_A))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_D))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_W))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_S))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_LCONTROL))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,1,0);
}
if(Input::getKey(Input::KEY_LSHIFT))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,1,0);
}
Here is the relevant part of the physics loop:
for(int i = 0; i < *numRigidBodies; i++)
{
rigidBodies[i].transform->getPos() += rigidBodies[i].velocity;
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
}
rigidBodies[0].angularVelocity = glm::quat();
rigidBodies[0].velocity = glm::vec3();
This works fine, but when I try raising angular velocity to a power with glm::pow, the object rotates randomly and does not follow my mouse. I realize I could do this with a line of code like
rigidBodies[i].transform->getRot() *= glm::angleAxis((float)Time::getFixedDelta() * glm::angle(rigidBodies[i].angularVelocity), glm::axis(rigidBodies[i].angularVelocity));
but this seems needlessly complicated for the task. What is causing this issue, and how can I fix it?
Not sure exactly how to do it with the API you're using, but basically, you would use Quaternion::Slerp(). Slerp means "spherical linear interpolation".
Something like this(pseudocode) should work:
auto& rot = rigidBodies[i].transform->getRot();
auto goal = rigidBodies[i].angularVelocity * rot;
rot = rot.slerp(rot, goal, Time::deltaTime);
Edit:
I should note that this is not how I would approach this problem. I would just store the rotation around the X and Y axis as scalars and construct a new quaternion from them each frame.
Please excuse the sloppy pseudo code:
// previous x and y positions, could probably be set in MouseDown event
float lastX = ...;
float lastY = ...;
float xRotation = 0;
float yRotation = 0;
float rotationSpeed = 1.0;
void OnMouseMove(float x, float y) {
float dx = x - lastX;
float dy = y - lastY;
lastX = x;
lastY = y;
xRotation += dy * rotationSpeed * Time::deltaTime;
yRotation += dx * rotationSpeed * Time::deltaTime;
rigidBodies[i].transform->getRot() = eulerQuat(xRotation, yRotation, 0);
}
Turns out angular velocity is usually represented as a 3d vector where the direction is the axis and the magnitude is the angular speed. Replace this line of code:
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
with this:
if(rigidBodies[i].angularVelocity != glm::vec3())
rigidBodies[i].transform->getRot() *= glm::quat(rigidBodies[i].angularVelocity * float(Time::getFixedDelta()));
and the physics system works as expected. The if check makes sure that angular speed is not 0.

SFML atan2 function and deceleration

if(wIsPressed){
movement.x += sin((player.getRotation() * 3.141592654)/ 180) * .5; //accelerates ship at a rate of 0.5 ms^2
movement.y -= cos((player.getRotation() * 3.141592654)/ 180) * .5; //accelerates ship at a rate of 0.5 ms^2
}
else if(abs(movement.x) > 0 || abs(movement.y) > 0){
double angle = (atan2(movement.x, movement.y) * 3.141592654) / 180; //finds angle of current movement vector and converts fro radians to degrees
movement.x -= sin((angle)) * 0.5; //slows down ship by 0.5 using current vector angle
movement.y += cos((angle)) * 0.5; //slows down ship by 0.5 using current vector angle
}
basically, what happens after using this code is that my ship is pulled directly down to the bottom of the screen, and acts like the ground has gravity, and i dont understand what i am doing incorrectly
To elaborate on my comment:
You're not converting your angle to degrees properly. It should be:
double angle = atan2(movement.x, movement.y) * 180 / 3.141592654;
However, you're using this angle in another trig calculation, and the C++ trig functions expect radians, so you really shouldn't be converting this to degrees in the first place. Your else if statement could also be causing problems, because you're checking for an absolute value greater than 0. Try something like this:
float angle = atan2(movement.x, movement.y);
const float EPSILON = 0.01f;
if(!wIsPressed && abs(movement.x) > EPSILON) {
movement.x -= sin(angle) * 0.5;
}
else {
movement.x = 0;
}
if(!wIsPressed && abs(movement.y) > EPSILON) {
movement.y += cos(angle) * 0.5;
}
else {
movement.y = 0;
}

Calculating iso tile co-ordinates for a TMX map when zoomed on a CCLayerPanZoom control

I'm working on some code to place isometric CCTMXTiledMap onto a CCLayerPanZoom control and then convert a touch location into ISO tilemap co-ordinates. This all works perfectly well for me, so long as the scale of the CClayerPanZoom is 1 (i.e. if I don't zoom in or zoom out). I can pan the map around and still calculate the correct iso tile co-oridinates. However, as soon as I zoom the tiled map in or out the iso cordinates returned by my code are completely wrong. Please see below for my code to calculate the iso co-ordinates from the touch location.
-(CGPoint) tilePosFromLocation:(CGPoint)location tileMap:(CCTMXTiledMap*)thisTileMap panZoom:(CCLayerPanZoom*)thisPanZoom
{
float midScale = (thisPanZoom.minScale + thisPanZoom.maxScale) / 2.0;
float newScale = (thisPanZoom.scale <= midScale) ? thisPanZoom.maxScale : thisPanZoom.minScale;
if (thisPanZoom.scale < 1)
{
newScale = newScale + thisPanZoom.scale;
}
else
{
newScale = newScale - thisPanZoom.scale;
}
CGFloat deltaX = (location.x - thisPanZoom.anchorPoint.x * (thisPanZoom.contentSize.width / CC_CONTENT_SCALE_FACTOR()) ) * (newScale);
CGFloat deltaY = (location.y - thisPanZoom.anchorPoint.y * (thisPanZoom.contentSize.height / CC_CONTENT_SCALE_FACTOR()) ) * (newScale);
CGPoint position = ccp((thisPanZoom.position.x - deltaX) , (thisPanZoom.position.y - deltaY) );
float halfMapWidth = thisTileMap.mapSize.width * 0.5f;
float mapHeight = thisTileMap.mapSize.height;
float tileWidth = thisTileMap.tileSize.width / CC_CONTENT_SCALE_FACTOR() * newScale;
float tileHeight = thisTileMap.tileSize.height / CC_CONTENT_SCALE_FACTOR() * newScale;
CGPoint tilePosDiv = CGPointMake(position.x / tileWidth, position.y / tileHeight );
float inverseTileY = tilePosDiv.y - (mapHeight * CC_CONTENT_SCALE_FACTOR()) * newScale; //mapHeight + tilePosDiv.y;
float posX = (int)(tilePosDiv.y - tilePosDiv.x + halfMapWidth);
float posY = (int)(inverseTileY + tilePosDiv.x - halfMapWidth + mapHeight);
// make sure coordinates are within isomap bounds
posX = MAX(0, posX);
posX = MIN(thisTileMap.mapSize.width - 1, posX);
posY = MAX(0, posY);
posY = MIN(thisTileMap.mapSize.height - 1, posY);
return CGPointMake(posX, posY);
}
Can anyone offer any insight into where I'm going wrong with this?
Thanks,
Alan

Problems with OpenGL rotation

In my OpenGL application, I have a camera which is controlled using the keyboard (movement) and mouse (looking around).
Everythings been working perfectly fine up until now, I have noticed that if I move my camera above 300 in the Y axis, it starts to mess up when moving the mouse. For example, if I go to Y =310, and move the mouse up, as it starts to look up it starts turning to the left as well.
I am not sure what the reason for this is. Can anyone help?
Heres the code to work out forward and up position for gluLookAt()
double cosR, cosP, cosY; //temp values for sin/cos from
double sinR, sinP, sinY; //the inputed roll/pitch/yaw
if(Yaw > 359) Yaw = 0;
if(Pitch > 359) Pitch = 0;
if(Yaw < 0) Yaw = 359;
if(Pitch < 0) Pitch = 359;
cosY = cosf(Yaw*3.1415/180);
cosP = cosf(Pitch*3.1415/180);
cosR = cosf(Roll*3.1415/180);
sinY = sinf(Yaw*3.1415/180);
sinP = sinf(Pitch*3.1415/180);
sinR = sinf(Roll*3.1415/180);
//forward position
forwardPos.x = sinY * cosP*360;
forwardPos.y = sinP * 360;
forwardPos.z = cosP * -cosY*360;
//up position
upPos.x = -cosY * sinR - sinY * sinP * cosR;
upPos.y = cosP * cosR;
upPos.z = -sinY * sinR - sinP * cosR * -cosY;
Gimble Lock. It is explained here.
Quaternions are the standard solution to this problem.
Briefly, when two axis angles approach each other in your above calculation, it causes a loss of available movement. You need a 4th degree of freedom to prevent this - and this is what Quaternion math allow.

How to get a change in mouse position difference using Cocoa?

I'm using opengl and trying to create a first person camera. All examples use GLUT and I need to get the mouse differential in cocoa. But I'm running into issues which appers to be tied with mouseMoved being called as soon as the mouse is moved (which is to be expected). Is there a way to make this mroe accurate? Or a simialer function like GLUTS glutMouseFunc?
Current attempt:
-(void)mouseMoved:(NSEvent *)event{
static bool wrap = false;
if(!wrap){
NSPoint eventLocation = [event locationInWindow];
float centerX = self.frame.size.width/2 + [self window].frame.origin.x;
float centerY = self.frame.size.height/2 + [self window].frame.origin.y;
CGPoint mousePointCenter = CGPointMake(centerX, centerY);
CGWarpMouseCursorPosition(mousePointCenter);
int dx = eventLocation.x - self.frame.size.width/2 ;
int dy = eventLocation.y - self.frame.size.height/2 ;
const float mousespeed = 0.001;
angles.x += dx * mousespeed;
angles.y += dy * mousespeed;
if(angles.x < -M_PI)
angles.x += M_PI * 2;
else if(angles.x > M_PI)
angles.x -= M_PI * 2;
if(angles.y < -M_PI / 2)
angles.y = -M_PI / 2;
if(angles.y > M_PI / 2)
angles.y = M_PI / 2;
lookat.x = sinf(angles.x) * cosf(angles.y);
lookat.y = sinf(angles.y);
lookat.z = cosf(angles.x) * cosf(angles.y);
CGWarpMouseCursorPosition(mousePointCenter);
[self setNeedsDisplay:YES];
}
else{
wrap = true;
}
}
I'm not sure I followed what your code is supposed to be doing, but repeatedly warping the mouse cursor to a center point is rarely the right thing to do.
First, you can use the deltaX and deltaY values of the NSEvent.
Perhaps you want to do CGAssociateMouseAndMouseCursorPosition(false) to disassociate the mouse from the cursor position. When you do that, the on-screen cursor no longer moves with the mouse. You can hide it or reposition it (yes, you'd warp it once in this case). Also, events no longer have changes in their absolute position. But they do still carry delta movement values which reflect the mouse movements.