Perspective camera coordinates to screen coordinates in libgdx - opengl

perspectiveCamera = new PerspectiveCamera(90, 80, 48);
perspectiveCamera.position.set(0,0, 10f);
perspectiveCamera.lookAt(0,0,0);
perspectiveCamera.near = .01f;
perspectiveCamera.far = 300f;
My ScreenWidth x ScreenHeight are 800 x 480;
pCamera.unproject(mytouchPoint) shall suppose to give results between
x = 0 to 80
y = 0 to 48
but I m getting 0.000xyz for both x and y axis

Don't use such a small value for your camera's near member, it will cause floating point errors and/or z-fighting.
The width and height values you provided to PerspectiveCamera constructor, are used to calculate the aspect ratio. There is no single 2D resolution (the size of the screen-plane in world coordinates) in a 3D perspective.
You cannot simply unproject a 2D screen coordinate to a single 3D coordinate. For each 2D screen coordinate, there are an "infinite" amount of 3D coordinates possible. Therefor the unproject method of the camera will use the z-coordinate of the provided screen coordinate to decide which of those 3D coordinates to return. If z is zero, it will give the coordinate on the near-plane. If z is one, it will give the coordinate on the far-plane.
Assuming you used z=0 for myTouchPoint and given you have a very small near-plane (since you near value is very small), the unprojected value will be vary small and therefor (almost) equal to zero.
For more information, you might want to have a look at: http://blog.xoppa.com/interacting-with-3d-objects/

I found a way to easily do it. Its fast too.
Just create a plane at required depth z and find intersection of ray on it.
float zDepth=-10;//your decision or and object z position
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Ray ray = camera.getPickRay(screenX,screenY);
Plane plane=new Plane();
plane.set(0,0,1,0);// the xy plane with direction z facing screen
plane.d=zDepth;//***** the depth in 3d for the coordinates
Vector3 yourVector3Position=new Vector3();
Intersector.intersectRayPlane(ray, plane, yourVector3Position);
}

Related

Drawing a sprite on the circumference of a circle based on the position of other objects

I'm making a sniper shooter arcade style game in Gamemaker Studio 2 and I want the position of targets outside of the viewport to be pointed to by chevrons that move along the circumference of the scope when it moves. I am using trig techniques to determine the coordinates but the chevron is jumping around and doesn't seem to be pointing to the target. I have the code broken into two: the code to determine the coordinates in the step event of the enemies class (the objects that will be pointed to) and a draw event in the same class. Additionally, when I try to rotate the chevron so it also points to the enemy, it doesn't draw at all.
Here's the coordinate algorithm and the code to draw the chevrons, respectively
//determine the angle the target makes with the player
delta_x = abs(ObjectPlayer.x - x); //x axis displacement
delta_y = abs(ObjectPlayer.y - y); //y axis displacement
angle = arctan2(delta_y,delta_x); //angle in radians
angle *= 180/pi //angle in radians
//Determine the direction based on the larger dimension and
largest_distance = max(x,y);
plusOrMinus = (largest_distance == x)?
sign(ObjectPlayer.x-x) : sign(ObjectPlayer.y-y);
//define the chevron coordinates
chevron_x = ObjectPlayer.x + plusOrMinus*(cos(angle) + 20);
chevron_y = ObjectPlayer.y + plusOrMinus*(sign(angle) + 20);
The drawing code
if(object_exists(ObjectEnemy)){
draw_text(ObjectPlayer.x, ObjectPlayer.y-10,string(angle));
draw_sprite(Spr_Chevron,-1,chevron_x,chevron_y);
//sSpr_Chevron.image_angle = angle;
}
Your current code is slightly more complex that it needs to be for this, if you want to draw chevrons pointing towards all enemies, you might as well do that on spot in Draw. And use degree-based functions if you're going to need degrees for drawing anyway
var px = ObjectPlayer.x;
var py = ObjectPlayer.y;
with (ObjectEnemy) {
var angle = point_direction(px, py, x, y);
var chevron_x = px + lengthdir_x(20, angle);
var chevron_y = py + lengthdir_y(20, angle);
draw_sprite_ext(Spr_Chevron, -1, chevron_x, chevron_y, 1, 1, angle, c_white, 1);
}
(also see: an almost-decade old blog post of mine about doing this while clamping to screen edges instead)
Specific problems with your existing code are:
Using a single-axis plusOrMinus with two axes
Adding 20 to sine/cosine instead of multiplying them by it
Trying to apply an angle to sSpr_Chevron (?) instead of using draw_sprite_ext to draw a rotated sprite.
Calculating largest_distance based on executing instance's X/Y instead of delta X/Y.

OpenglGL - how to check if cursor coincides with object region?

I am trying to implement a logic where, on mouse click, a shot is fired at an object.To do so, I did the following,
I first considered the .obj file of my model and found the region (list of coordinates) that the shot works on (a particular weak point of the body).
I then considered the least and largest x,y and z values present in the file for that particular region (xmin,ymin,zmin and xmax,ymax,zmax).
To figure out whether the shot has landed on the weak point, I considered the assumption that a shot would land on the weak point, if the coordinates of the shot lie between (xmin,ymin,zmin) and (xmax,ymax,zmax).
I assumed the coordinates from the .obj file to be the actual coordinates of the model, since the assimp code I have directly loads in the coordinates of the model. Considering (xmin,ymin,zmin) and (xmax,ymax,zmax), I converted the coordinates to window coordinates via gluProject().
I then considered the current cursor position and checked if the cursor position lies between (xmin,ymin,zmin) and (xmax,ymax,zmax).
The problems I now face are:
The object coordinates provided in the .obj file range between -4 to 4, which then lie around 1.0 after gluProject(), whereas the cursor position lies between (0,0) and (1280,720).
After gluProject(), (xmin,ymin) and (xmax,ymax) are either (0,1) or (1,0) whereas the zmin and zmax values seem fine.
How can I get my logic working ?
Here is the code:
// Call shader to draw and acquire necessary information for gluProject()
modelShader.use();
modelShader.setMat4("projection", projection);
modelShader.setMat4("view", view);
glm::mat4 model_dragon;
double time=glfwGetTime();
model_dragon=glm::translate(model_dragon, glm::vec3(cos((360.0-time)/2.0)*60.0,cos(((360.0-time)/2.0))*(-2.5),sin((360-time)/1.0)*60.0));
model_dragon=glm::rotate(model_dragon,(float)(glm::radians(30.0)),glm::vec3(0.0,0.0,1.0));
model_dragon=glm::scale(model_dragon,glm::vec3(1.4,1.4,1.4));
modelShader.setMat4("model", model_dragon);
collision_model=model_dragon;collision_view=view;collision_proj=projection; //so that I can provide the view,model and projection required for gluProject()
ourModel.Draw(modelShader);
Mouse button callback
// Note: dragon_min and dragon_max variables hold the constant position of the min and max coordinates.
void mouse_button_callback(GLFWwindow* window,int button,int action,int mods){
if(button==GLFW_MOUSE_BUTTON_LEFT && action==GLFW_PRESS){
Mix_PlayChannel( -1, shot, 0 ); //Play sound
GLdouble x,y,xmin,ymin,zmin,xmax,ymax,zmax,dmodel[16],dproj[16];
GLint dview[16];
float *model = (float*)glm::value_ptr(collision_model);
float *proj = (float*)glm::value_ptr(collision_proj);
float *view = (float*)glm::value_ptr(collision_view);
for (int i = 0; i < 16; ++i){dmodel[i]=model[i];dproj[i]=proj[i];dview[i]=(int)view[i];} // Convert mat4 to double array
glfwGetCursorPos(window,&x,&y);
gluProject(dragon_min_x,dragon_min_y,dragon_min_z,dmodel,dproj,dview,&xmin,&ymin,&zmin);
gluProject(dragon_max_x,dragon_max_y,dragon_max_z,dmodel,dproj,dview,&xmax,&ymax,&zmax);
if((x>=xmin && x<=xmax) && (y>=ymin && y<=ymax)){printf("Hit\n");defense--;}
The .obj coordinates have eg. values as shown:
0.032046 1.533727 4.398055
You are confusing the parameters of gluProject, especially the view parameter. This parameter should contain 4 integers which describe the viewport (x,y,width,height) and not the view matrix.
gluProject (and a lot of other glu functions) are tailored towards the fixed function pipeline and their matrix stacks. Due to this, you have to pass the following information:
model: The modelview matrix, as returned by glGetDoublev( GL_MODELVIEW_MATRIX, ...)).
proj: The projection matrix, as returned by glGetDoublev( GL_PROJECTION_MATRIX, ...).
view: The current viewport, as returned by glGetIntegerv( GL_VIEWPORT, ...)
As you see, the view matrix is packed together with the model matrix and view contains the viewport.
I'd strongly advice not to use glu functions at all when working with modern OpenGL. Especially when the matrices are already stored in glm, it would be better to use glm::project.
Note1: Converting a floating point matrix to an integer matrix by casting each element almost never results in anything meaningful.
Note2: When projecting a bounding rectangle to screenspace, the result will in general not be a rectangle anymore. During projection, angles are not preserved, thus the result is a general four cornered polygon and not a rectangle anymore. Same goes for bounding boxes: You can't even guarantee that the projected box is contained in the screen-space rectangle defined by projecting [x_min, y_min, z_min] and [x_max, y_max, z_max].

Error control of directx camera rotation?

I use the mouse to control camera rotation in my program(using Directx 9.0c). Mouse X controls the camera to rotate around the Up Vector and Mouse Y controls the rotation around the Right Vector. Rotation caculation is as below:
void Camera::RotateCameraUp(float angle)
{
D3DXMATRIX RoMatrix;
D3DXMatrixRotationAxis(&RoMatrix, &vUp, angle);
D3DXVec3TransformCoord(&vLook, &vLook, &RoMatrix);
D3DXVec3TransformCoord(&vRight, &vRight, &RoMatrix);
}
void Camera::RotateCameraRight(float angle)
{
D3DXMATRIX RoMatrix;
D3DXMatrixRotationAxis(&RoMatrix, &vRight, angle);
D3DXVec3TransformCoord(&vLook, &vLook, &RoMatrix);
D3DXVec3TransformCoord(&vUp, &vUp, &RoMatrix);
}
It is supposed that rotation around Up or Right vector should not leads to rotation around the "LookAt" vector, but if I circle my mouse for a while and stop it at the starting point, rotation around the "LookAt" vector has happened. I think it's because of the error while caculating, but I don't know how to eliminate it or control it. Any idea?
This is a common problem. You apply many rotations, and over time, the rounding errors sum up. After a while, the three vectors vUp, vLook and vRight are not normalized and orthogonal anymore.
I would use one of two options:
1.
Don't store vLook and vRight; instead, just store 2 angles. Assuming x is right, y is top, z is back, store a) the angle between your view axis and the xz-Plane, and b) the angle between the projection of your view axis on the xz-Plane and the z-Axis or x-Axis. Update these angles according to mouse move and calculate vLook and vRight from them.
2.
Set the y-component of vRight to 0, as vRight should be in the xz-Plane. Then re-orthonormalize the vectors (you know the vectors should be perpendicular to each other and have length 1). So after calculating the new vLook and vRight, apply these corrections:
vRight.y = 0
vRight = Normalize(vRight)
vUp = Normalize(Cross(vLook, vRight))
vLook = Normalize(Cross(vRight, vUp))

World-Coordinate Issues with gluUnProject()

I'm currently calling Trace (method below) from a game loop. Right now all I'm trying to do is get the world coordinates from the screen mouse so I can move objects around in the world space. The values I'm getting from gluUnProject are however; puzzling me.
I was using glReadPixel(...) to get the Z value but that produced little to no movement in the object I was drawing and the resulting vector ended up being the same as my cameras location (except for the tiny decimal changes due to mouse movement), so I decided to get rid of the call and replace the Z value with 1.
My question is: Does the following code look right to you? Every example I've seen thusfar is either identical or -very- similar but I can't seem to produce correct results, even if I lock down the Y axis. If the code is correct, then I'm guessing that I'm just not using the resulting vector properly. Should I not be able to draw an object or point directly with the resulting vector or do I have to do something else with it, like normalize?
The current render mode is GL_RENDER and I am using glFrustum with a NearZ value of 1 and FarZ value of 2048, to create a perspective. There is also a series of viewports created along with scissors, with a size and width of 512x768 and positioned in each corner of a 1024x768 window. Trace(...) is called in between rendering of the upper left viewport and is the only perspective projection, while the other viewports are orthographic. FOV is set to 45.
void VideoWindow::Trace(int cursorX, int cursorY)
{
double objX, objY, objZ;//holder for world coordinates
GLint view[4];//viewport dimensions+pos
GLdouble p[16];//projection matrix
GLdouble m[16];//modelview matrix
GLdouble z;//Z-Buffer Value?
glGetDoublev (GL_MODELVIEW_MATRIX, m);
glGetDoublev (GL_PROJECTION_MATRIX,p);
glGetIntegerv( GL_VIEWPORT, view );
//view[3]-cursorY = conversion from upper left (0,0) to lower left (0,0)
//Unproject 2D Screen coordinates into wonderful world coordinates
gluUnProject(cursorX, view[3]-cursorY, 1, m, p, view, &objX, &objY, &objZ);
//Do something useful here???
}
Any ideas?
Edit: I've changed the winZ value to 0.5 instead of 1 which gives a vector thats more reasonable but drawing a point still wasn't matching the mouse. I found out that the value of view[3] was 384 which is correct for the viewport I'm using but I replaced it with 768 (the actual window size) and the point followed the mouse 100%. Further experimentation reveals that I can't use the coordinates to move around a 3D object in the perspective world space using this these coordinates however moving around 3D object in Orthographic space works fine.
The winz argument to gluUnproject specifies the depth from the camera at which you're "picking" your points. As you've stated this coordinate should be in the [0, 1] range.
Some tutorials like NeHes read out the z coordinate from the depth buffer so that you "pick" at the right depth, of course for this to work you'll have to do the gluUnproject after you've rendered everything else.
Regardless, if you set winz to 0.5 or something (not 0 or 1 or the point will end up on the near or far clip plane, and maybe culled) and do the following:
gluUnProject(cursorX, view[3]-cursorY, 0.5, m, p, view, &objX, &objY, &objZ);
//Do something useful here???
glPointSize(10);
glBegin(GL_POINTS);
glColor3f(1, 0, 0);
glVertex3f(objX, objY, objZ);
glEnd();
You should end up with a red blob at the mouse pointer (provided nothing else overdraws it afterwards and you don't have any funny render states which renders the point invisible).
just a thought, but if the third argument to gluUnProject is the z distance to the camera, wouldn't any point you draw at that location be on the near clipping plane of your frustum?
Better make that z value a bit higher.

3d model to fit in viewport

How does a 3D model handled unit wise ?
When i have a random model that i want to fit in my view port i dunno if it is too big or not, if i need to translate it to be in the middle...
I think a 3d object might have it's own origine.
You need to find a bounding volume, a shape that encloses all the object's vertices, for your object that is easier to work with than the object itself. Spheres are often used for this. Either the artist can define the sphere as part of the model information or you can work it out at run time. Calculating the optimal sphere is very hard, but you can get a good approximation using the following:
determine the min and max value of each point's x, y and z
for each vertex
min_x = min (min_x, vertex.x)
max_x = max (max_x, vertex.x)
min_y = min (min_y, vertex.y)
max_y = max (max_y, vertex.y)
min_z = min (min_z, vertex.z)
max_z = max (max_z, vertex.z)
sphere centre = (max_x + min_x) / 2, (max_y + min_y) / 2, (max_z + min_z) / 2
sphere radius = distance from centre to (max_x, max_y, max_z)
Using this sphere, determine the a world position that allows the sphere to be viewed in full - simple geometry will determine this.
Sorry, your question is very unclear. I suppose you want to center a 3D model to a viewport. You can achieve this by calculating the model's bounding box. To do this, traverse all polygons and get the minimum/maximum X/Y/Z coordinates. The bounding box given by the points (min_x,min_y,min_z) and (max_x,max_y,max_z) will contain the whole model. Now you can center the model by looking at the center of this box. With some further calculations (depending on your FOV) you can also get the left/right/upper/lower borders inside your viewport.
"so i tried to scale it down"
The best thing to do in this situation is not to transform your model at all! Leave it be. What you want to change is your camera.
First calculate the bounding box of your model somewhere in 3D space.
Next calculate the radius of it by taking the max( aabb.max.x-aabb.min.x, aabb.max.y-aabb.min.y, aabb.max.z-aabb.min.z ). It's crude but it gets the job done.
To center the object in the viewport place the camera at the object position. If Y is your forward axis subtract the radius from Y. If Z is the forward axis then subtract radius from it instead. Subtract a fudge factor to get you past the pesky near plane so your model doesn't clip out. I use quaternions in my engine with a nice lookat() method. So call lookat() and pass in the center of the bounding box. Voila! You're object is centered in the viewport regardless of where it is in the world.
This always places the camera axis aligned so you might want to get fancy and transform the camera into model space instead, subtract off the radius, then lookat() the center again. Then you're always looking at the back of the model. The key is always the lookat().
Here's some example code from my engine. It checks to see if we're trying to frame a chunk of static terrain, if so look down from a height, or a light or a static mesh. A visual is anything that draws in the scene and there are dozens of different types. A Visual::Instance is a copy of the visual, or where to draw it.
void EnvironmentView::frameSelected(){
if( m_tSelection.toInstance() ){
Visual::Instance& I = m_tSelection.toInstance().cast();
Visual* pVisual = I.toVisual();
if( pVisual->isa( StaticTerrain::classid )){
toEditorCamera().toL2W().setPosition( pt3( 0, 0, 50000 ));
toEditorCamera().lookat( pt3( 0 ));
}else if( I.toFlags()->bIsLight ){
Visual::LightInstance& L = static_cast<Visual::LightInstance&>( I );
qst3& L2W = L.toL2W();
const sphere s( L2W.toPosition(), L2W.toScale() );
const f32 y =-(s.toCenter()+s.toRadius()).y();
const f32 z = (s.toCenter()+s.toRadius()).y();
qst3& camL2W = toEditorCamera().toL2W();
camL2W.setPosition(s.toCenter()+pt3( 0, y, z ));//45 deg above
toEditorCamera().lookat( s.toCenter() );
}else{
Mesh::handle hMesh = pVisual->getMesh();
if( hMesh ){
qst3& L2W = m_tSelection.toInstance()->toL2W();
vec4x4 M;
L2W.getMatrix( M );
aabb3 b0 = hMesh->toBounds();
b0.min = M * b0.min;
b0.max = M * b0.max;
aabb3 b1;
b1 += b0.min;
b1 += b0.max;
const sphere s( b1.toSphere() );
const f32 y =-(s.toCenter()+s.toRadius()*2.5f).y();
const f32 z = (s.toCenter()+s.toRadius()*2.5f).y();
qst3& camL2W = toEditorCamera().toL2W();
camL2W.setPosition( L2W.toPosition()+pt3( 0, y, z ));//45 deg above
toEditorCamera().lookat( b1.toOrigin() );
}
}
}
}