I'm using C++, Allegro5, and codeblocks. I'm trying to write a test program before I write a very basic Asteroids knock-off. The only rules are use C++, Allegro, and no sprites or bitmaps (this specifically has to use primitives like lines and shapes).
I'm drawing the shape in quadrant1, redrawing it in quadrant2 translated from a relative origin, redrawing it in three rotated by certain number of degrees, and finally drawing it rescaled in quadrant4.
Parts 1 2 and 4 work fine. My problem is the rotate function. It is not rotating the correct amount, or not rotating at all. At less than 90 it rotates half of the entered degrees. After that it doesn't rotate or doesnt draw at all.
I'll post rotation method code below. I know it's bloated and needs to be cleaned up (I just overhauled some of the logic to fix 1, 2, and 4), but I really need help with rotation.
I think I'm using atan2 correctly and I've turned
void rotation (float degrees)
{
ALLEGRO_COLOR color_black = al_map_rgb(0,0,0);
ALLEGRO_COLOR color_blue = al_map_rgb(150,150,150);
ALLEGRO_COLOR color_orange = al_map_rgb(255,135,135);
ALLEGRO_COLOR color_red = al_map_rgb(255,0,0);
al_clear_to_color(al_map_rgb(255,255,255)); //clear screen to white
//draw black grid
al_draw_line(400,0, 400,600, color_black, 4.0);
al_draw_line(0,300, 800,300, color_black, 4.0);
al_draw_line(200,0, 200,600, color_black, 1.0);
al_draw_line(600,0, 600,600, color_black, 1.0);
al_draw_line(0,150, 800,150, color_black, 1.0);
al_draw_line(0,450, 800,450, color_black, 1.0);
float rx[13], ry[13];
float phi, theta, radius; //phi original angle theta added angle of rotation
float ycenter = 0, xcenter = 0; //later will be used with varying object position
theta = degrees * PI/180.0;
for (int i = 0; i < 13; i++)
{
phi = atan2(yarray[i], xarray[i]);
radius = sqrt(pow((xarray[i]-xcenter),2.0) + pow((yarray[i]-ycenter),2.0));
rx[i] = xarray[i] + radius * cos(phi + theta);
ry[i] = yarray[i] + radius * sin(phi + theta);
}
float x = quad[2][0];
float y = quad[2][1];
al_draw_triangle(x+rx[0],y+ry[0], x+rx[1],y+ry[1], x+rx[2],y+ry[2], color_red, 10);
al_draw_filled_triangle(x+rx[3],y+ry[3], x+rx[4],y+ry[4], x+rx[5],y+ry[5], color_orange);
al_draw_filled_triangle(x+rx[6],y+ry[6], x+rx[7],y+ry[7], x+rx[8],y+ry[8], color_blue); //body
al_draw_filled_triangle(x+rx[9],y+ry[9], x+rx[10],y+ry[10], x+rx[11],y+ry[11],color_blue); //wing
al_draw_filled_ellipse(x+rx[12],y+ry[12], 4, 8, color_black); //cockpit
al_flip_display();
}
I found teh answser after working it all out with paper and LOTS of print statements. I had to remove the xarray and yarray addition for rx and ry. See below.
rx[i] = radius * cos(phi + theta);
ry[i] = radius * sin(phi + theta);
replaces
rx[i] = xarray[i] + radius * cos(phi + theta);
ry[i] = yarray[i] + radius * sin(phi + theta);
Related
I'm trying to make a shooting tank in OpenGL on C++.
I've drawn the tank and he moves on sin shaped ground, and I want to keep the end of the cannon in variables named currX, currY. My display function is:
void display()
{
double dx, dy, beta;
glClear(GL_COLOR_BUFFER_BIT); // clean frame buffer
// DrawSky();
DrawGround(); // draws a y = 0.075 * sin(x * 10) shaped ground
glPushMatrix();
// rotation of 2*PI equals 2*PI*Radius of Wheel
// in our case the wheel is rotated each time by angle (in degrees)
dx = direction * 2 * PI * 0.03 * (angle / 360);
dy = 0.075 * sin(dx * 10);
beta = atan(0.075 * 10 * cos(dx * 10)); // derrivative
beta *= 180 / PI; // transforms beta to degrees
glTranslated(dx, dy, 0);
if (direction == 1)
{
glRotated(180, 0, 1, 0);
beta = -beta;
}
glScaled(0.3, 0.3, 1);
glRotated(beta, 0, 0, 1);
DrawTank();
glPopMatrix();
//....
}
Now the problem is if I know that the tank cannon end was P = (-0.285,0.4175) before it was scaled and translated what will be the x,y of the cannon end at the end?
I've tried to multiply P by 0.3 which is the scaling factor in both x,y axis and its close to the end but not exactly there. What should I do?
should I calculate the currX,currY in the DrawTank or in the display?
in the image below the white point should be located at the end of the cannon.
So I am drawing a sphere not using the "subdividing icosahedron" approach but using triangle strips and parameteric equation of the sphere.
Here is my code
glBegin(GL_TRIANGLE_SRIP);
for(float i = -PI/2; i < PI/2; i+= 0.01f)
{
temp = i+0.01f;
for(float j = 0; j < 2*PI; j+=0.01f)
{
temp -= 0.01f;
glVertex3f( cx + rad * cos(j) * cos(temp), cy + rad * cos(temp) * sin(j), cz + rad * sin(temp));
temp += 0.01f;
glVertex3f( cx + rad * cos(j) * cos(temp), cy + rad * cos(temp) * sin(j), cz + rad * sin(temp));
}
}
glEnd();
The approach is as followes. Imagine a Circle in the XY plane. This is drawn using the inner loop. Now imagine the XY plane moved above or below in the Z-axis and the radius changed cause it's a sphere. This is done using the outer loop.
The first triangle coordinate is given for the Circle when XY plane is at its initial position. After temp+=0.01f the plane moved up by 0.01 and the second triangle vertex coordinate is given. This is how the strip is calculated.
The problem is if cx = cy = cz = 0 or any low value like 2 or 3 the sphere seems fine. However if I put for e.g cx = 15, cy = 15, cz = -6 the sphere gets deformed. Here is the picture.
If i use GL_POINTS this is what im getting
Sorry a very stupid mistake, I wasn't converting the values i put in glFrustum correctly hence a weird FOV was being generated. Solved the issue now. Thanks
I'm trying to set up a google maps style zoom-to-cursor control for my opengl camera. I'm using a similar method to the one suggested here. Basically, I get the position of the cursor, and calculate the width/height of my perspective view at that depth using some trigonometry. I then change the field of view, and calculate how to much I need to translate in order to keep the point under the cursor in the same apparent position on the screen. That part works pretty well.
The issue is that I want to limit the fov to be less than 90 degrees. When it ends up >90, I cut it in half and then translate everything away from the camera so that the resulting scene looks the same as with the larger fov. The equation to find that necessary translation isn't working, which is strange because it comes from pretty simple algebra. I can't find my mistake. Here's the relevant code.
void Visual::scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
glm::mat4 modelview = view*model;
glm::vec4 viewport = { 0.0, 0.0, width, height };
float winX = cursorPrevX;
float winY = viewport[3] - cursorPrevY;
float winZ;
glReadPixels(winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
glm::vec3 screenCoords = { winX, winY, winZ };
glm::vec3 cursorPosition = glm::unProject(screenCoords, modelview, projection, viewport);
if (isinf(cursorPosition[2]) || isnan(cursorPosition[2])) {
cursorPosition[2] = 0.0;
}
float zoomFactor = 1.1;
// = zooming in
if (yoffset > 0.0)
zoomFactor = 1/1.1;
//the width and height of the perspective view, at the depth of the cursor position
glm::vec2 fovXY = camera.getFovXY(cursorPosition[2] - zTranslate, width / height);
camera.setZoomFromFov(fovXY.y * zoomFactor, cursorPosition[2] - zTranslate);
//don't want fov to be greater than 90, so cut it in half and move the world farther away from the camera to compensate
//not working...
if (camera.Zoom > 90.0 && zTranslate*2 > MAX_DEPTH) {
float prevZoom = camera.Zoom;
camera.Zoom *= .5;
//need increased distance between camera and world origin, so that view does not appear to change when fov is reduced
zTranslate = cursorPosition[2] - tan(glm::radians(prevZoom)) / tan(glm::radians(camera.Zoom) * (cursorPosition[2] - zTranslate));
}
else if (camera.Zoom > 90.0) {
camera.Zoom = 90.0;
}
glm::vec2 newFovXY = camera.getFovXY(cursorPosition[2] - zTranslate, width / height);
//translate so that position under the cursor does not appear to move.
xTranslate += (newFovXY.x - fovXY.x) * (winX / width - .5);
yTranslate += (newFovXY.y - fovXY.y) * (winY / height - .5);
updateView = true;
}
The definition of my view matrix. Called ever iteration of the main loop.
void Visual::setView() {
view = glm::mat4();
view = glm::translate(view, { xTranslate,yTranslate,zTranslate });
view = glm::rotate(view, glm::radians(camera.inclination), glm::vec3(1.f, 0.f, 0.f));
view = glm::rotate(view, glm::radians(camera.azimuth), glm::vec3(0.f, 0.f, 1.f));
camera.Right = glm::column(view, 0).xyz();
camera.Up = glm::column(view, 1).xyz();
camera.Front = -glm::column(view, 2).xyz(); // minus because OpenGL camera looks towards negative Z.
camera.Position = glm::column(view, 3).xyz();
updateView = false;
}
Field of view helper functions.
glm::vec2 getFovXY(float depth, float aspectRatio) {
float fovY = tan(glm::radians(Zoom / 2)) * depth;
float fovX = fovY * aspectRatio;
return glm::vec2{ 2*fovX , 2*fovY };
}
//you have a desired fov, and you want to set the zoom to achieve that.
//factor of 1/2 inside the atan because we actually need the half-fov. Keep full-fov as input for consistency
void setZoomFromFov(float fovY, float depth) {
Zoom = glm::degrees(2 * atan(fovY / (2 * depth)));
}
The equations I'm using can be found from the diagram here. Since I want to have the same field of view dimensions before and after the angle is changed, I start with
fovY = tan(theta1) * d1 = tan(theta2) * d2
d2 = (tan(theta1) / tan(theta2)) * d1
d1 = distance between camera and cursor position, before fov change = cursorPosition[2] - zTranslate
d2 = distance after
theta1 = fov angle before
theta2 = fov angle after = theta1 * .5
Appreciate the help.
So I'm trying to figure out how to mannually create a camera class that creates a local frame for camera transformations. I've created a player object based on OpenGL SuperBible's GLFrame class.
I got keyboard keys mapped to the MoveUp, MoveRight and MoveForward functions and the horizontal and vertical mouse movements are mapped to the xRot variable and rotateLocalY function. This is done to create a FPS style camera.
The problem however is in the RotateLocalY. Translation works fine and so does the vertical mouse movement but the horizontal movement scales all my objects down or up in a weird way. Besides the scaling, the rotation also seems to restrict itself to 180 degrees and rotates around the world origin (0.0) instead of my player's local position.
I figured that the scaling had something to do with normalizing vectors but the GLframe class (which I used for reference) never normalized any vectors and that class works just fine. Normalizing most of my vectors only solved the scaling and all the other problems were still there so I'm figuring one piece of code is causing all these problems?
I can't seem to figure out where the problem lies, I'll post all the appropriate code here and a screenshot to show the scaling.
Player object
Player::Player()
{
location[0] = 0.0f; location[1] = 0.0f; location[2] = 0.0f;
up[0] = 0.0f; up[1] = 1.0f; up[2] = 0.0f;
forward[0] = 0.0f; forward[1] = 0.0f; forward[2] = -1.0f;
}
// Does all the camera transformation. Should be called before scene rendering!
void Player::ApplyTransform()
{
M3DMatrix44f cameraMatrix;
this->getTransformationMatrix(cameraMatrix);
glRotatef(xAngle, 1.0f, 0.0f, 0.0f);
glMultMatrixf(cameraMatrix);
}
void Player::MoveForward(GLfloat delta)
{
location[0] += forward[0] * delta;
location[1] += forward[1] * delta;
location[2] += forward[2] * delta;
}
void Player::MoveUp(GLfloat delta)
{
location[0] += up[0] * delta;
location[1] += up[1] * delta;
location[2] += up[2] * delta;
}
void Player::MoveRight(GLfloat delta)
{
// Get X axis vector first via cross product
M3DVector3f xAxis;
m3dCrossProduct(xAxis, up, forward);
location[0] += xAxis[0] * delta;
location[1] += xAxis[1] * delta;
location[2] += xAxis[2] * delta;
}
void Player::RotateLocalY(GLfloat angle)
{
// Calculate a rotation matrix first
M3DMatrix44f rotationMatrix;
// Rotate around the up vector
m3dRotationMatrix44(rotationMatrix, angle, up[0], up[1], up[2]); // Use up vector to get correct rotations even with multiple rotations used.
// Get new forward vector out of the rotation matrix
M3DVector3f newForward;
newForward[0] = rotationMatrix[0] * forward[0] + rotationMatrix[4] * forward[1] + rotationMatrix[8] * forward[2];
newForward[1] = rotationMatrix[1] * forward[1] + rotationMatrix[5] * forward[1] + rotationMatrix[9] * forward[2];
newForward[2] = rotationMatrix[2] * forward[2] + rotationMatrix[6] * forward[1] + rotationMatrix[10] * forward[2];
m3dCopyVector3(forward, newForward);
}
void Player::getTransformationMatrix(M3DMatrix44f matrix)
{
// Get Z axis (Z axis is reversed with camera transformations)
M3DVector3f zAxis;
zAxis[0] = -forward[0];
zAxis[1] = -forward[1];
zAxis[2] = -forward[2];
// Get X axis
M3DVector3f xAxis;
m3dCrossProduct(xAxis, up, zAxis);
// Fill in X column in transformation matrix
m3dSetMatrixColumn44(matrix, xAxis, 0); // first column
matrix[3] = 0.0f; // Set 4th value to 0
// Fill in the Y column
m3dSetMatrixColumn44(matrix, up, 1); // 2nd column
matrix[7] = 0.0f;
// Fill in the Z column
m3dSetMatrixColumn44(matrix, zAxis, 2); // 3rd column
matrix[11] = 0.0f;
// Do the translation
M3DVector3f negativeLocation; // Required for camera transform (right handed OpenGL system. Looking down negative Z axis)
negativeLocation[0] = -location[0];
negativeLocation[1] = -location[1];
negativeLocation[2] = -location[2];
m3dSetMatrixColumn44(matrix, negativeLocation, 3); // 4th column
matrix[15] = 1.0f;
}
Player object header
class Player
{
public:
//////////////////////////////////////
// Variables
M3DVector3f location;
M3DVector3f up;
M3DVector3f forward;
GLfloat xAngle; // Used for FPS divided X angle rotation (can't combine yaw and pitch since we'll also get a Roll which we don't want for FPS)
/////////////////////////////////////
// Functions
Player();
void ApplyTransform();
void MoveForward(GLfloat delta);
void MoveUp(GLfloat delta);
void MoveRight(GLfloat delta);
void RotateLocalY(GLfloat angle); // Only need rotation on local axis for FPS camera style. Then a translation on world X axis. (done in apply transform)
private:
void getTransformationMatrix(M3DMatrix44f matrix);
};
Applying transformations
// Clear screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// Apply camera transforms
player.ApplyTransform();
// Set up lights
...
// Use shaders
...
// Render the scene
RenderScene();
// Do post rendering operations
glutSwapBuffers();
and mouse
float mouseSensitivity = 500.0f;
float horizontal = (width / 2) - mouseX;
float vertical = (height / 2) - mouseY;
horizontal /= mouseSensitivity;
vertical /= (mouseSensitivity / 25);
player.xAngle += -vertical;
player.RotateLocalY(horizontal);
glutWarpPointer((width / 2), (height / 2));
Honestly I think you are taking a way to complicated approach to your problem. There are many ways to create a camera. My favorite is using a R3-Vector and a Quaternion, but you could also work with a R3-Vector and two floats (pitch and yaw).
The setup with two angles is simple:
glLoadIdentity();
glTranslatef(-pos[0], -pos[1], -pos[2]);
glRotatef(-yaw, 0.0f, 0.0f, 1.0f);
glRotatef(-pitch, 0.0f, 1.0f, 0.0f);
The tricky part now is moving the camera. You must do something along the lines of:
flaot ds = speed * dt;
position += tranform_y(pich, tranform_z(yaw, Vector3(ds, 0, 0)));
How to do the transforms, I would have to look that up, but you could to it by using a rotation matrix
Rotation is trivial, just add or subtract from the pitch and yaw values.
I like using a quaternion for the orientation because it is general and thus you have a camera (any entity that is) that independent of any movement scheme. In this case you have a camera that looks like so:
class Camera
{
public:
// lots of stuff omitted
void setup();
void move_local(Vector3f value);
void rotate(float dy, float dz);
private:
mx::Vector3f position;
mx::Quaternionf orientation;
};
Then the setup code uses shamelessly gluLookAt; you could make a transformation matrix out of it, but I never got it to work right.
void Camera::setup()
{
// projection related stuff
mx::Vector3f eye = position;
mx::Vector3f forward = mx::transform(orientation, mx::Vector3f(1, 0, 0));
mx::Vector3f center = eye + forward;
mx::Vector3f up = mx::transform(orientation, mx::Vector3f(0, 0, 1));
gluLookAt(eye(0), eye(1), eye(2), center(0), center(1), center(2), up(0), up(1), up(2));
}
Moving the camera in local frame is also simple:
void Camera::move_local(Vector3f value)
{
position += mx::transform(orientation, value);
}
The rotation is also straight forward.
void Camera::rotate(float dy, float dz)
{
mx::Quaternionf o = orientation;
o = mx::axis_angle_to_quaternion(horizontal, mx::Vector3f(0, 0, 1)) * o;
o = o * mx::axis_angle_to_quaternion(vertical, mx::Vector3f(0, 1, 0));
orientation = o;
}
(Shameless plug):
If you are asking what math library I use, it is mathex. I wrote it...
I have defined 2 points on the surface of a sphere using spherical coordinates.
// define end point positions
float theta_point_1 = (5/10.0)*M_PI;
float phi_point_1 = (5/10.0)*2*M_PI;
float x_point_1 = Radius * sin(theta_point_1) * cos(phi_point_1);
float y_point_1 = Radius * sin(theta_point_1) * sin(phi_point_1);
float z_point_1 = Radius * cos(theta_point_1);
float theta_point_2 = (7/10.0)*M_PI;
float phi_point_2 = (1/10.0)*2*M_PI;
float x_point_2 = Radius * sin(theta_point_2) * cos(phi_point_2);
float y_point_2 = Radius * sin(theta_point_2) * sin(phi_point_2);
float z_point_2 = Radius * cos(theta_point_2);
// draw end points
void end_points ()
{
glColor3f (1.0, 1.0, 1.0);
glPointSize(25.0);
glBegin(GL_POINTS);
glVertex3f(x_point_1,y_point_1,z_point_1);
glVertex3f(x_point_2,y_point_2,z_point_2);
glEnd();
}
To step between the two points, I do the following:
find the difference between theta_points_1,2 and phi_points_1,2
divide the differences by 'n' (yielding 's')
redraw 'n' times, while stepping up the theta and phi by 's' each time
In the following, I've defined the differences between my theta and phi values, divided them, and then redraw them.
// begining spherical coords
float theta_point_1_value=5;
float phi_point_1_value=5;
// ending sperical coords
float theta_point_2_value=7;
float phi_point_2_value=1;
// dividing the difference evenly
float step_points=30;
float step_theta = 2/step_points;
float step_phi = 4/step_points;
// step between spherical coordinates
void stepping_points ()
{
glColor3f (1.0, 0.0, 0.0);
for (int i = 1; i < step_points; i++)
{
float theta = (theta_point_1_value/10.0)*M_PI;
float phi = (phi_point_1_value/10.0)*2*M_PI;
float x = Radius * sin(theta) * cos(phi);
float y = Radius * sin(theta) * sin(phi);
float z = Radius * cos(theta);
glPushMatrix();
glTranslatef(x,y,z);
glutSolidSphere (0.05,10,10);
glPopMatrix();
}
glEnd();
}
Now I understand, this displays 30 solid spheres at the same position. Because I have NOT included 'step_theta' or 'step_phi' in any of the redraws.
And that is the root of my question. How do I employ 'step_theta' and 'step_phi' in my redraws?
What I want to do is say something like this at the top of my 'for' loop:
for (int i = 1; i < step_points; i++)
{
float theta_point_1_value = (theta_point_1_value+step_theta);
float phi_point_1_value = (phi_point_1_value+step_phi);
float theta = (theta_point_1_value/10.0)*M_PI;
float phi = (phi_point_1_value/10.0)*2*M_PI;
float x = Radius * sin(theta) * cos(phi);
float y = Radius * sin(theta) * sin(phi);
float z = Radius * cos(theta);
glPushMatrix();
glTranslatef(x,y,z);
glutSolidSphere (0.05,10,10);
glPopMatrix();
}
The above will redraw 30 solid spheres, but they don't show between my defined end points. It's pretty clear that either my math or syntax is screwy (or more than likely, both are).
Hint: What is the range of your loop variable, i? What do you want the range of your step_theta and step_phi to be?
When you declare a variable inside the loop, it goes out of scope and is destructed after every iteration. As such, only the value of i changes between your loop iterations.
Also: Consider using a vector/point class. (x_point_1, y_point_1) is not C++ :).
If you want consistent timing regardless of frame rate, you need to track the passage of time and use that to control how far you interpolate between the two points. Remember the start time and calculate the desired end time, then each frame, calculate (float)(now-start)/(end-start). This will give you a value between 0.0 and 1.0. Multiply that value by the delta of each spherical coordinate and add their start angles and you'll get what angles you need to be at now.