Using Monotouch and OpenTK I am trying to get the screen coordinate of one 3D point. I have my world view projection matrix set up, and OpenGL makes sense of it and projects my 3D model perfectly, but how to use the same matrix to project just one point from 2D to 3D?
I thought I could simply use:
Vector3.Transform(ref input3Dpos, ref matWorldViewProjection, out projected2Dpos);
Then have the projected screen coordinate in projected2DPos. But the resulting Vector4 does not seem to represent the proper projected screen coordinate. And I do not know how to calculate it from there on.
I found I need to divide by Vector4.w, however I am still getting the wrong values. Using this method now:
private static bool GluProject(OpenTK.Vector3 objPos, OpenTK.Matrix4 matWorldViewProjection, int[] viewport, out OpenTK.Vector3 screenPos)
{
OpenTK.Vector4 _in;
_in.X = objPos.X;
_in.Y = objPos.Y;
_in.Z = objPos.Z;
_in.W = 1f;
Vector4 _out = OpenTK.Vector4.Transform(_in, matWorldViewProjection);
if (_out.W <= 0.0)
{
screenPos = OpenTK.Vector3.Zero;
return false;
}
_out.X /= _out.W;
_out.Y /= _out.W;
_out.Z /= _out.W;
/* Map x, y and z to range 0-1 */
_out.X = _out.X * 0.5f + 0.5f;
_out.Y = -_out.Y * 0.5f + 0.5f;
_out.Z = _out.Z * 0.5f + 0.5f;
/* Map x,y to viewport */
_out.X = _out.X * viewport[2] + viewport[0];
_out.Y = _out.Y * viewport[3] + viewport[1];
screenPos.X = _out.X;
screenPos.Y = _out.Y;
screenPos.Z = _out.Z;
return true;
}
I cannot see any errors though... :S
In the first question you're missing the last step: Mapping from NDC (Normalized Device Coordinates) to viewport coordinates. That's what the lines
/* Map x,y to viewport */
_out.X = _out.X * viewport[2] + viewport[0];
_out.Y = _out.Y * viewport[3] + viewport[1];
in your GluProject do,
You have two options. You can calculate it yourself, or use the glProject function. I prefer the first.
Number 1:
private Vector2 Convert(
Vector3 pos,
Matrix4 viewMatrix,
Matrix4 projectionMatrix,
int screenWidth,
int screenHeight)
{
pos = Vector3.Transform(pos, viewMatrix);
pos = Vector3.Transform(pos, projectionMatrix);
pos.X /= pos.Z;
pos.Y /= pos.Z;
pos.X = (pos.X + 1) * screenWidth / 2;
pos.Y = (pos.Y + 1) * screenHeight / 2;
return new Vector2(pos.X, pos.Y);
}
Number 2:
public Vector2 form3Dto2D(Vector3 our3DPoint)
{
Vector3 our2DPoint;
float[] modelviewMatrix = new float[16];
float[] projectionMatrix = new float[16];
int[] viewport = new int[4];
GL.GetFloat(GetPName.ModelviewMatrix, modelviewMatrix);
GL.GetFloat(GetPName.ProjectionMatrix, projectionMatrix);
GL.GetInteger(GetPName.Viewport, viewport);
OpenTK.Graphics.Glu.Project(our3DPoint, convertFloatsToDoubles(modelviewMatrix),
convertFloatsToDoubles(projectionMatrix), viewport, out our2DPoint);
return new Vector2(our2DPoint.X, our2DPoint.Y)
}
public static double[] convertFloatsToDoubles(float[] input)
{
if (input == null)
{
return null; // Or throw an exception - your choice
}
double[] output = new double[input.Length];
for (int i = 0; i < input.Length; i++)
{
output[i] = input[i];
}
return output;
}
Related
I'm making a level editor for my game with OpenGL in C++. I'm trying to make Editor Camera just like in Unity Engine 2D Scene Camera, but I have an issue when I try to implement mouse movement for the camera (Camera Panning). I'm converting mouse position from screen to world space.
ScreenToWorldSpace Method:
Vector3 Application::ScreenToWorldSpace(int mousex, int mousey)
{
double x = 2.0 * mousex / viewportWidth - 1;
double y = 2.0 * mousey / viewportHeight - 1;
Vector4 screenPos = Vector4(x, -y, -1.0f, 1.0f);
Matrix4 ProjectionViewMatrix = camera1->GetProjectionMatrix() * camera1->GetViewMatrix();
Matrix4 InverseProjectionViewMatrix = glm::inverse(ProjectionViewMatrix);
Vector4 worldPos = InverseProjectionViewMatrix * screenPos;
return Vector3(worldPos);
}
The above method works correctly.
But I'm using ScreenToWorldSpace coordinates to update camera position.
Render Method:
void Application::Render(float deltaTime)
{
Vector3 pos = ScreenToWorldSpace(mousePosition.x, mousePosition.y);
// This is the position of a tile not the camera
position = Vector3(0, 0, 0);
Vector3 rotation = Vector3(0, 0, 0);
Vector3 scale = Vector3(1);
Matrix4 translationMatrix = glm::translate(Matrix4(1.0f), position);
Matrix4 rotationMatrix = glm::eulerAngleYXZ(rotation.y, rotation.x, rotation.z);
Matrix4 scaleMatrix = glm::scale(Matrix4(1.0f), scale);
modelMatrix = translationMatrix * rotationMatrix * scaleMatrix;
if (mouseButtonDown)
{
Console << pos.x << ", " << pos.y << Endl;
camera1->position = Vector3(pos.x, pos.y, -10);
}
{
glScissor(0, 0, 900, 600);
glEnable(GL_SCISSOR_TEST);
glClearColor(236 / 255.0f, 64 / 255.0f, 122 / 255.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, 900, 600);
basicShader->Use();
dirt_grass_tex->Use();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
camera1->SetZoom(zoomFactor);
camera1->Update();
Matrix4 mvp = camera1->GetProjectionMatrix() * camera1->GetViewMatrix() * modelMatrix;
basicShader->SetUniformMat4("MVP", mvp);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glDisable(GL_SCISSOR_TEST);
}
}
Camera Class:
#include "camera.h"
Camera::Camera(int width, int height)
{
swidth = width;
sheight = height;
position = Vector3(0, 0, -10);
rotation = Vector3(0, 0, 0);
m_direction = Vector3(0, 0, -5);
m_up = Vector3(0, 1, 0);
m_right = Vector3(1, 0, 0);
m_offset = Vector3(-swidth / 2 * m_zoom, -sheight / 2 * m_zoom, 0);
m_projection = glm::ortho(0.0f * m_zoom, (float)swidth * m_zoom, 0.0f * m_zoom, (float)sheight * m_zoom, -1000.0f, 0.0f);
}
Camera::~Camera()
{
}
void Camera::Update()
{
Vector3 finalPos = position + m_offset;
m_up = glm::cross(m_right, m_direction);
m_viewMatrix = glm::lookAt(finalPos, finalPos + m_direction, m_up);
m_viewMatrix = glm::scale(m_viewMatrix, Vector3(100));
}
void Camera::SetZoom(float zoom)
{
m_zoom = zoom;
m_offset = Vector3(-swidth / 2 * m_zoom, -sheight / 2 * m_zoom, 0);
m_projection = glm::ortho(0.0f * m_zoom, (float)swidth * m_zoom, 0.0f * m_zoom, (float)sheight * m_zoom, -1000.0f, 0.0f);
}
The following is the output I get when I try to move camera with mouse position converted from Screen to World Space:
if (mouseButtonDown)
{
Console << pos.x << ", " << pos.y << Endl;
position = Vector3(pos.x, pos.y, 0);
}
But if I use mouse position converted from Screen to World space using ScreenToWorldSpace Method the object moves perfectly. Have a look at the following gif:
Following is what I'm trying to achieve:
So I'm Trying to make Game Engine Editor, in that I want to implement Editor Scene Camera like unity / unreal engine scene camera. Following is the editor I'm currently working on:
I tried looking into different resources, but i'm clueless. Help me understand how to move the camera with mouse.
What I think is happening:
Since I'm converting mouse position from screen to world space using camera's projectionView matrix and using those world coordinates to move camera position is causing the problem, because when ever camera moves, projectionView is updated which in turn changes mouse position relative to viewMatrix recursively.
I would Appreciate some help.
Ordinarily, you wouldn't want to write the mouse position directly into the camera location (because that will be of limited use in practice - whenever you click on the screen, the camera would jump).
What you probably want to do something along these lines:
Vector3 g_lastPosition;
void onMousePressed(int x, int y) {
// record starting position!
g_lastPosition = ScreenToWorldSpace(x, y);
}
void onMouseMove(int x, int y) {
// find the difference between new position, and last, in world space
Vector3 new_pos = ScreenToWorldSpace(x, y);
Vector3 offset = new_pos - g_lastPosition;
g_lastPosition = new_pos;
// now move camera by offset
camera->position += offset
}
If you are in an orthographic view, then really you don't need to worry about the projection matrix at all.
int g_lastX;
int g_lastY;
void onMousePressed(int x, int y) {
// store mouse pos
g_lastX = x;
g_lastY = y;
}
void onMouseMove(int x, int y) {
// find the difference between new position, and last, in pixels
int offsetX = x - g_lastX;
int offsetY = y - g_lastY;
// update mouse pos
g_lastX = x;
g_lastY = y;
// get as ratio +/- 1
float dx = ((float) offsetX) / swidth;
float dy = ((float) offsetY) / sheight;
// now move camera by offset (might need to multiply by 2 here?)
camera->position.x += camera->m_offset.x * dx;
camera->position.y += camera->m_offset.y * dy;
}
But in general, for any mouse based movement, you always want to be thinking in terms of adding an offset, rather than setting an exact position.
I'm trying to use bullet physics to draw a ray to hit a game object in the scene so I can select it, i am using the camera matrix to draw a ray and then pick an object in space and then look through a list of game objects and look for the same location.
On Mouse press I have the following code, it seems to be off and only picks the items some times:
glm::vec4 lRayStart_NDC(
((float)lastX / (float)RECT_WIDTH - 0.5f) * 2.0f,
((float)lastY / (float)RECT_HEIGHT - 0.5f) * 2.0f,
-1.0,
1.0f
);
glm::vec4 lRayEnd_NDC(
((float)lastX / (float)RECT_WIDTH - 0.5f) * 2.0f,
((float)lastY / (float)RECT_HEIGHT - 0.5f) * 2.0f,
0.0,
1.0f
);
projection = glm::perspective(glm::radians(SceneManagement::getInstance()->MainCamera->GetVOW()), (float)RECT_WIDTH / (float)RECT_HEIGHT, 0.1f, 100.0f);
glm::mat4 InverseProjectionMatrix = glm::inverse(projection);
view = SceneManagement::getInstance()->MainCamera->GetViewMatrix();
glm::mat4 InverseViewMatrix = glm::inverse(view);
glm::vec4 lRayStart_camera = InverseProjectionMatrix * lRayStart_NDC;
lRayStart_camera /= lRayStart_camera.w;
glm::vec4 lRayStart_world = InverseViewMatrix * lRayStart_camera;
lRayStart_world /= lRayStart_world.w;
glm::vec4 lRayEnd_camera = InverseProjectionMatrix * lRayEnd_NDC;
lRayEnd_camera /= lRayEnd_camera.w;
glm::vec4 lRayEnd_world = InverseViewMatrix * lRayEnd_camera;
lRayEnd_world /= lRayEnd_world.w;
glm::vec3 lRayDir_world(lRayEnd_world - lRayStart_world);
lRayDir_world = glm::normalize(lRayDir_world);
glm::vec3 out_end = SceneManagement::getInstance()->MainCamera->GetCamPosition() + SceneManagement::getInstance()->MainCamera->GetCamFront() * 1000.0f;
btCollisionWorld::ClosestRayResultCallback RayCallback(
btVector3(SceneManagement::getInstance()->MainCamera->GetCamPosition().x, SceneManagement::getInstance()->MainCamera->GetCamPosition().y, SceneManagement::getInstance()->MainCamera->GetCamPosition().z),
btVector3(out_end.x, out_end.y, out_end.z)
);
PhysicsManager::getInstance()->dynamicsWorld->rayTest(
btVector3(SceneManagement::getInstance()->MainCamera->GetCamPosition().x, SceneManagement::getInstance()->MainCamera->GetCamPosition().y, SceneManagement::getInstance()->MainCamera->GetCamPosition().z),
btVector3(out_end.x, out_end.y, out_end.z),
RayCallback
);
if (RayCallback.hasHit())
{
btTransform position = RayCallback.m_collisionObject->getInterpolationWorldTransform();
printf("Collision \n");
for (int i = 0; i < SceneManagement::getInstance()->gObjects.size(); i++)
{
if (SceneManagement::getInstance()->gObjects.at(i)->transform.Position.x == position.getOrigin().getX() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.y == position.getOrigin().getY() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.z == position.getOrigin().getZ())
{
int select = i;
SceneManagement::getInstance()->SelectedGameObject = SceneManagement::getInstance()->gObjects.at(select);
SceneManagement::getInstance()->SelectedGameObject->DisplayInspectorUI();
return;
}
}
}
This check
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.x == position.getOrigin().getX() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.y == position.getOrigin().getY() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.z == position.getOrigin().getZ()
fails due to numerical precision issues.
You should check the "equality" of vectors only up to some precision.
Please, use some distance function and the following check instead of your if() condition:
const double EPSILON = 1e-4; // experiment with this value
auto objPos = SceneManagement::getInstance()->gObjects.at(i)->transform.Position;
bool isWithinRange = distance3D(objPos, position.getOrigin()) < EPSILON;
if (isWithinRange)
{
int select = i;
...
}
The distance3D function is simply the euclidean distance in 3D. The glm::distance function should do, just convert both vectors to glm::vec3 format.
I'm trying to implement frustum culling to my voxel engine, basically I'm rendering chunks and I want to cull every chunk that is outside of the frustum of the camera. I tried a lot of different approaches and code that I found on the web, but yet I can't get it to work. The algorithm is in two parts:
• First I'm extracting the frustum planes from the projectionview matrix.
• Then I'm checking for each chunk if it is inside or colliding with the frustum.
The behavior is generally the same: when looking from the origin to positive direction it seems to work, but when looking to the negative direction it doesn't, and when I'm going away from the origin it starts breaking and doing non-sense. Also when looking up and down the culling is weird.
Here is my frustum plane extraction:
public static Plane[] frustumPlanes(Matrix4f mat, boolean normalize)
{
Plane[] p = new Plane[6];
p[0] = normalizePlane(mat.m30 + mat.m00, mat.m31 + mat.m01, mat.m32 + mat.m02, mat.m33 + mat.m03); // left
p[1] = normalizePlane(mat.m30 - mat.m00, mat.m31 - mat.m01, mat.m32 - mat.m02, mat.m33 - mat.m03); // right
p[2] = normalizePlane(mat.m30 - mat.m10, mat.m31 - mat.m11, mat.m32 - mat.m12, mat.m33 - mat.m13); // top
p[3] = normalizePlane(mat.m30 + mat.m10, mat.m31 + mat.m11, mat.m32 + mat.m12, mat.m33 + mat.m13); // bottom
p[4] = normalizePlane(mat.m30 + mat.m20, mat.m31 + mat.m21, mat.m32 + mat.m22, mat.m33 + mat.m23); // near
p[5] = normalizePlane(mat.m30 - mat.m20, mat.m31 - mat.m21, mat.m32 - mat.m22, mat.m33 - mat.m23); // far
return p;
}
public static Plane normalizePlane(float A, float B, float C, float D) {
float nf = 1.0f / (float)Math.sqrt(A * A + B * B + C * C);
return new Plane(new Vector3f(nf * A, nf * B, nf * C), nf * D);
}
mat is the projectionview matrix, here is the projection matrix:
private void createProjectionMatrix() {
float aspectRatio = (float) DisplayManager.WIDTH / (float) DisplayManager.HEIGHT;
float y_scale = (float) ((1f / Math.tan(Math.toRadians(FOV / 2f))));
float x_scale = y_scale / aspectRatio;
float frustum_length = FAR_PLANE - NEAR_PLANE;
projectionMatrix = new Matrix4f();
projectionMatrix.m00 = x_scale;
projectionMatrix.m11 = y_scale;
projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustum_length);
projectionMatrix.m23 = -1;
projectionMatrix.m32 = -((2 * NEAR_PLANE * FAR_PLANE) / frustum_length);
projectionMatrix.m33 = 0;
}
Here is the view matrix:
public static Matrix4f createViewMatrix(Camera camera) {
Matrix4f viewMatrix = new Matrix4f();
viewMatrix.setIdentity();
Matrix4f.rotate((float) Math.toRadians(camera.getRotation().x), new Vector3f(1, 0, 0), viewMatrix, viewMatrix);
Matrix4f.rotate((float) Math.toRadians(camera.getRotation().y), new Vector3f(0, 1, 0), viewMatrix, viewMatrix);
Matrix4f.rotate((float) Math.toRadians(camera.getRotation().z), new Vector3f(0, 0, 1), viewMatrix, viewMatrix);
Vector3f cameraPos = camera.getPosition();
Vector3f negativeCameraPos = new Vector3f(-cameraPos.x,-cameraPos.y,-cameraPos.z);
Matrix4f.translate(negativeCameraPos, viewMatrix, viewMatrix);
return viewMatrix;
}
Here is the collision detection code aabb vs plane:
public static int boxToPlaneCollision(Plane plane, Vector3f[] minMax)
{
int result = 2; //Inside
// planes have unit-length normal, offset = -dot(normal, point on plane)
int nx = plane.normal.x > 0?1:0;
int ny = plane.normal.y > 0?1:0;
int nz = plane.normal.z > 0?1:0;
// getMinMax(): 0 = return min coordinate. 1 = return max.
float dot = (plane.normal.x*minMax[nx].x) + (plane.normal.y*minMax[nx].y) + (plane.normal.z*minMax[nx].z);
if ( dot < -plane.offset )
return 0; //Outside
float dot2 = (plane.normal.x*minMax[1-nx].x) + (plane.normal.y*minMax[1-nx].y) + (plane.normal.z*minMax[1-nx].z);
if ( dot2 <= -plane.offset )
result = 1; //Intersect
return result;
}
And finally here is where everything is called:
public boolean chunkInsideFrustum(Vector3f chunkPos) {
Vector3f chunkPosMax = new Vector3f(chunkPos.x + Terrain.CHUNK_SIZE, Terrain.CHUNK_HEIGHT, chunkPos.z + Terrain.CHUNK_SIZE);
for (int i = 0; i < 6; i++) {
if(Collider.boxToPlaneCollision(frustumPlanes[i], new Vector3f[] {chunkPos,chunkPosMax}) == 0)
return false;
}
return true;
}
I'm using openGL with LWJGL 2 (Java).
My questions are:
Where is the problem? In the frustum plane extraction code? In the collision detection?
and
I saw people calculating the frustum with projection and modelview matrix, what about this technique? is it better?
Thank you very much for your help!
EDIT:
for the second question, I saw here Extracting View Frustum Planes (Gribb & Hartmann method) someone posted that:
The missing part:
comboMatrix = projection_matrix * Matrix4_Transpose(modelview_matrix)
And then he did the exact same algorithm that I did to extract the planes, but what is modelview_matrix? What model should I use?
i need to implement arcball camera. I got something similar, but it works very crookedly (the angle changes sharply, when turning to the right / left, the camera raises up / down strongly).
Here is my source code, can you tell me where I went wrong:
bool get_arcball_vec(double x, double y, glm::vec3& a)
{
glm::vec3 vec = glm::vec3((2.0 * x) / window.getWidth() - 1.0, 1.0 - (2.0 * y) / window.getHeight(), 0.0);
if (glm::length(vec) >= 1.0)
{
vec = glm::normalize(vec);
}
else
{
vec.z = sqrt(1.0 - pow(vec.x, 2.0) - pow(vec.y, 2.0));
}
a = vec;
return true;
}
...
void onMouseMove(double x, double y) {
if (rightMouseButtonPressed) {
glm::vec3 a,b;
cur_mx = x;
cur_my = y;
if (cur_mx != last_mx || cur_my != last_my)
if (get_arcball_vec(last_mx, last_my, a) && get_arcball_vec(cur_mx, cur_my, b))
viewport.getCamera().orbit(a,b);
last_mx = cur_mx;
last_my = cur_my;
...
void Camera::orbit(glm::vec3 a, glm::vec3 b)
{
forward = calcForward();
right = calcRight();
double alpha = acos(glm::min(1.0f, glm::dot(b, a)));
glm::vec3 axis = glm::cross(a, b);
glm::mat4 rotationComponent = glm::mat4(1.0f);
rotationComponent[0] = glm::vec4(right, 0.0f);
rotationComponent[1] = glm::vec4(up, 0.0f);
rotationComponent[2] = glm::vec4(forward, 0.0f);
glm::mat4 toWorldCameraSpace = glm::transpose(rotationComponent);
axis = toWorldCameraSpace * glm::vec4(axis, 1.0);
glm::mat4 orbitMatrix = glm::rotate(glm::mat4(1.0f), (float)alpha, axis);
eye = glm::vec4(target, 1.0) + orbitMatrix * glm::vec4(eye - target, 1.0f);
up = orbitMatrix * glm::vec4(up, 1.0f);
}
I use this code to map 2D mouse position to the sphere:
Vector3 GetArcBallVector(const Vector2f & mousePos) {
float radiusSquared = 1.0; //squared radius of the sphere
//compute mouse position from the centre of screen to interval [-half, +half]
Vector3 pt = Vector3(
mousePos.x - halfScreenW,
halfScreenH - mousePos.y,
0.0f
);
//if length squared is smaller than sphere diameter
//point is inside
float lengthSqr = pt.x * pt.x + pt.y * pt.y;
if (lengthSqr < radiusSquared){
//inside
pt.z = std::sqrtf(radiusSquared - lengthSqr);
}
else {
pt.z = 0.0f;
}
pt.z *= -1;
return pt;
}
To calculate rotation, I use the last (startPt) and current (endPt) mapped position and do:
Quaternion actRot = Quaternion::Identity();
Vector3 axis = Vector3::Cross(endPt, startPt);
if (axis.LengthSquared() > MathUtils::EPSILON) {
float angleCos = Vector3::Dot(endPt, startPt);
actRot = Quaternion(axis.x, axis.y, axis.z, angleCos);
}
I prefer to use Quaternions over matrices since they are easy to multiply (for acumulated rotation) and interpolate (for some smooting).
I am using legacy OpenGL and trying to move vertices around with the mouse. To test whether a vertex is clicked on I loop through all vertices and multiply them by the model and projection matrix before dividing by the w value. This works fine and is shown below:
for (Vertex *vertex : context->getMesh().vertices) {
QVector4D vert(vertex->xPos, vertex->yPos, vertex->zPos, 1.0f);
QVector4D transformedVert = projectionMatrix * modelMatrix * vert;
transformedVert /= transformedVert.w();
if ((mappedX < (transformedVert.x() + 0.1) && mappedX > (transformedVert.x() - 0.1)) &&
(mappedY < (transformedVert.y() + 0.1) && mappedY > (transformedVert.y() - 0.1))) {
std::cout << "SUCCESS" << std::endl;
vertexPicked = true;
currentVertex = vertex;
}
}
Then when I move the mouse I try to work backwards by first multiplying the current mouse coordinates by the same W value as in the first step and then multiplying by the inverse of the projection and model matrices. This moves the vertex around but not to where the mouse is.
float mouseX = ((2.0f * event->x()) / width() - 1.0f);
float mouseY = -((2.0f * event->y()) / height() - 1.0f);
float x = (modelMatrix.inverted() * projectionMatrix.inverted() *
(QVector4D(mouseX, mouseY, 1, 1) * (projectionMatrix * modelMatrix * QVector4D(MousePicker::currentVertex->xPos, MousePicker::currentVertex->yPos, MousePicker::currentVertex->zPos, 1)).w())).x();
MousePicker::currentVertex->xPos = x;
I am currently only trying to change the X coordinate.