2013-11-28 150 views
1

我正在使用Open GL製作帶三角投影的遊戲。我有以下作爲視圖投影變換:將鼠標轉換爲屏幕位置

float aspect = 1024.f/768.f; 
glm::ortho<float>(-aspect*5, aspect*5, -5, 5, 0, 20) * glm::lookAt(glm::vec3(-std::sin(M_PI*36/180.f)*10,sin(M_PI*34/180.f)*10,10), glm::vec3(), glm::vec3(0,1,0)); 

屏幕分辨率爲1024x768。我想將鼠標映射到屏幕上的位置。例如,我遊戲中的瓷磚是1x1。當我將鼠標懸停在第一個tile(源自屏幕中心)時,我希望鼠標的位置介於(0,0)和(1,1)之間。我不知道如何做到這一點。我走到這一步,是通過執行此操作轉換到屏幕(即正交投影)的線性視圖:

glm::vec4 mousePos(x/768*10-apsect*5, 0, y/768*10-5, 0); 

但是我沒有哪裏從那裏去的線索。

+0

這個問題的答案可以幫助你 http://stackoverflow.com/questions/18244678/3d-ray-picking-use-mouse-coordinates-when-mouse-isnt-locked –

+0

我試圖按照什麼(glm :: vec3 pos = glm :: unProject(glm :: vec3(x,y,0)),view,ortho,glm :: vec4(0,0,1024, 768));'但是這並沒有產生我需要的結果。如果我刪除了視圖部分並只使用了一個單位矩陣,我得到了和前面一樣將x和y轉換爲縱橫比的結果。 – user975989

回答

1

這是非常類似的東西等軸接口,用於iPad的項目我的工作。這與您的項目具有相同的屏幕分辨率。當我第一次看到你的問題時,我幾天前正在構建其他項目代碼。但是需要構建拾取對象的接口代碼。所以嘗試開發代碼是有意義的。我使用空白彩色棋盤安裝了一個測試來適應你的問題。

這是一個快速的視頻演示。請原諒它的醜陋外觀。另請注意關於視頻,遊標後面的值是整數,但代碼產生浮點值。還有另一個功能是我爲了自己的使用而做的額外工作。如果這是你想要的東西,我會把它包含在我的答案中。

http://youtu.be/JyddfSf57ic

Screen shot of isometric checkerboard

此代碼是故意冗長,因爲我在數學生鏽。所以我花了很多時間在最近幾天重新學習點和跨產品,也讀重心代碼,但決定反對它。

代碼之前的最後一件事:問題中的投影矩陣存在問題。您在投影矩陣中包含相機變換。從技術上講,這是允許的,但網上有很多資源表明這是不好的做法。

希望這會有所幫助!

void calculateTouchPointOnGrid(CGPoint screenTouch) { 
    float backingWidth = 1024; 
    float backingHeight = 768; 
    float aspect = backingWidth/backingHeight; 
    float zNear = 0; 
    float zFar = 20; 
    glm::detail::tvec3<float> unprojectedNearZ; 
    glm::detail::tvec3<float> unprojectedFarZ; 

    /* 
    Window coordinates, including zNear 
    This code uses zNear and zFar as two arbitrary values of 
      magnitude along the direction (vector) of the camera's 
      view (affected by projection and model-view matrices) that enable 
    determining the line/ray, originating from screenTouch (or 
      mouse click, etc) that later intersects the plane. 
    */ 

    glm::vec3 win = glm::vec3(screenTouch.x, backingHeight - screenTouch.y, zNear); 


    // Model & View matrix 

    glm::detail::tmat4x4<float> modelTransformMatrix = glm::detail::tmat4x4<float>(1); 
    modelTransformMatrix = glm::translate(modelTransformMatrix, glm::detail::tvec3<float>(0,0,-1)); 
    modelTransformMatrix = glm::rotate(modelTransformMatrix, 0.f, glm::detail::tvec3<float>(0,1,0)); 
    modelTransformMatrix = glm::scale(modelTransformMatrix, glm::detail::tvec3<float>(1,1,1)); 

    glm::detail::tmat4x4<float> modelViewMatrix = lookAtMat * modelTransformMatrix; 


    /* 
    Projection 
    */ 
    const glm::detail::tmat4x4<float> projectionMatrix = 
     glm::ortho<float>(-aspect*5, aspect*5, -5, 5, 0, 20); 


    /* 
    Viewport 
    */  
    const glm::vec4 viewport = glm::vec4(0, 0, backingWidth, backingHeight); 

    /* 
    Calculate two points on a line/ray based on the window coordinate (including arbitrary Z value), plus modelViewMatrix, projection matrix and viewport 
    */ 
    unprojectedNearZ = glm::unProject(win, 
           modelViewMatrix, 
           projectionMatrix, 
           viewport); 
    win[2] = zFar; 
    unprojectedFarZ = glm::unProject(win, 
           modelViewMatrix, 
           projectionMatrix, 
           viewport); 


      /* 
    Define the start of the ray 
    */ 

    glm::vec3 rayStart(unprojectedNearZ[0], unprojectedNearZ[1], unprojectedNearZ[2]); 

    /* 
    Determine the vector traveling parallel to the camera from the two 
     unprojected points 
    */ 

    float lookatVectX = unprojectedFarZ[0] - unprojectedNearZ[0]; 
    float lookatVectY = unprojectedFarZ[1] - unprojectedNearZ[1]; 
    float lookatVectZ = unprojectedFarZ[2] - unprojectedNearZ[2]; 

    glm::vec3 dir(lookatVectX, lookatVectY, lookatVectZ); 



    /* 
    Define three points on the plane that will define a triangle. 
    Winding order does not matter. 
    */ 

    glm::vec3 p0(0,0,0); 
    glm::vec3 p1(1,0,0); 
    glm::vec3 p2(0,0,1); 

    /* 
    And finally the destination of the calculations 
    */ 

    glm::vec3 linePlaneIntersect; 


    if (cartesianLineIntersectPlane(rayStart, dir, p0, p1, p2, linePlaneIntersect)) { 
     // do work here using the linePlaneIntersect values 
    } 
} 


bool cartesianLineIntersectPlane(glm::vec3 rayStart, glm::vec3 dir, 
           glm::vec3 p0, glm::vec3 p1, glm::vec3 p2, glm::vec3 &linePlaneIntersect) { 

    /* 
    Create edge vectors to form the plane normal 
    */ 
    glm::vec3 edge1 = p1 - p0; 
    glm::vec3 edge2 = p2 - p0; 

    /* 
    Check if the ray direction is parallel to plane, before continuing 
    */ 

    glm::vec3 perpendicularvector = glm::cross(dir, edge2); 


    /* 
    dot product of edge1 on perpendicular vector 
    if orthogonal (approximately 0) then ray is parallel to plane, has 0 or infinite contact points 
    */ 

    float det = glm::dot(edge1, perpendicularvector); 
    float Epsilon = std::numeric_limits<float>::epsilon(); 
    if (det > -Epsilon && det < Epsilon) 
     // Parallel, return false 
     return false; 


    /* 
    Calculate the normalized/unit normal vector 
    */ 

    glm::vec3 planeNormal = glm::normalize(glm::cross(edge1, edge2)); 


    /* 
    Calculate d, the dot product of the normal and any point on the plane. 
    D is the magnitude of the plane's normal, to the origin. 
    */ 

    float d = planeNormal[0] * p0[0] + planeNormal[1] * p0[1] + planeNormal[2] * p0[2]; 


    /* 
    Take the x,y,z equations, ie: 
    finalP.xyz = raystart.xyz + dir.xyz * t 

    substitute them into the scalar equation for plane, ie: 
    ax + by + cz = d 

    and solve for t. (This gets a bit wordy) t is the 
    multiplier on the direction vector, originating at raystart, 
    where it intersects the plane. 


    eg: 

    ax + by + cz = d 
    a(raystart.x + dir.x * t) + b(raystart.y + dir.y * t) + c(raystart.z + dir.z * t) = d 
    a(raystart.x) + a(dir.x*t) + b(raystart.y) + b(dir.y*t) + c(raystart.z) + c(dir.z*t) = d 
    a(raystart.x) + a(dir.x*t) + b(raystart.y) + b(dir.y*t) + c(raystart.z) + c(dir.z*t) - d = 0 
    a(raystart.x) + b(raystart.y) + c(raystart.z) - d = - a(dir.x*t) - b(dir.y*t) - c(dir.z*t) 
    (a(raystart.x) + b(raystart.y) + c(raystart.z) - d)/(a(-dir.x) + b(-dir.y) + c(-dir.z) = t 

    */ 

    float leftsideScalars = (planeNormal[0] * rayStart[0] + 
          planeNormal[1] * rayStart[1] + 
          planeNormal[2] * rayStart[2] - d); 
    float directionDotProduct = (planeNormal[0] * (-dir[0]) + 
           planeNormal[1] * (-dir[1]) + 
           planeNormal[2] * (-dir[2])); 


    /* 
    Final calculation of t, hurrah! 
    */ 

    float t = leftsideScalars/directionDotProduct; 


    /* 
    This is the particular value of t for that line that lies 
    in the plane. Then you can solve for x, y, and z by going 
    back up to the line equations and substituting t back in. 
    */ 

    linePlaneIntersect = glm::vec3(rayStart[0] + dir[0] * t, 
            rayStart[1] + dir[1] * t, 
            rayStart[2] + dir[2] * t 
            ); 
    return true; 
} 
+0

謝謝你。當您調用cartesianLineIntersectPlane時,您正在使用您尚未定義的變量。我假設你重新命名了它們來幫助我理解,但我不知道哪些意思是哪個。 – user975989

+0

@ user975989我的測試代碼與測試代碼相比,我提供的答案更詳細。我正在清理我的答案。 –

+0

在完成了所有不必要的數學因爲我的遊戲的保證後,這個結果很好,我只剩下一個非常簡單的計算。 – user975989

1

你有屏幕空間座標,你想把它轉換爲模型空間。

你必須應用倒數從屏幕空間返回到模型空間。

GLM有一個名爲unProject一個很好的功能,這是否給你看的示例代碼1

你的問題是,你看到屏幕空間作爲一個點您的鼠標座標。鼠標的位置應該被看作是一個正在注視着你的光標的光線。如果這只是一個點,你實際上對它的信息很少。

所以,如果你的鼠標有一個2d座標作爲屏幕空間,你需要把這個2d點轉換成兩個不同的z座標值的三維座標)。

然後,您可以取消投影這些3d點以獲得兩個模型空間點。這兩點就代表你的鼠標。見示例代碼2.

然後從三維射線,您可以返回到平鋪座標,通過簡單地執行射線與平鋪平面的交點。見示例代碼3

示例代碼1

我添加的完整代碼在這個例子中,這樣就可以用數值玩,看看會發生什麼。

#include <glm/glm.hpp> 
#include <glm/gtc/matrix_transform.hpp> 
#include <iostream> 
#include <cmath> 

using namespace glm; 
using namespace std; 

int main() 
{ 
    //Our Matrices 
    float aspect = 1024.f/ 768; 
    vec4 viewPort = vec4(0.f,0.f,1024.f,768.f); 
    mat4 projectionMatrix = ortho<float>(0, 1024, 0, 768, 0, 20); 
    mat4 modelWorldMatrix = lookAt(vec3(-sin(M_PI*36/180.f)*10,sin(M_PI*34/180.f)*10,10), vec3(), vec3(0,1,0)); 

    //Our position 
    vec3 pos = vec3(1.0f,2.0f,3.0f); 

    //Tranform it into screen space 
    vec3 transformed = project(pos, modelWorldMatrix, projectionMatrix, viewPort); 
    cout << transformed.x << " " << transformed.y << " " << transformed.z << endl; 

    //Transform it back 
    vec3 untransformed = unProject(transformed, modelWorldMatrix, projectionMatrix, viewPort); 
    cout << untransformed.x << " " << untransformed.y << " " << untransformed.z << endl; 

    //You'll see how the glm's unproject works 

    return 0; 
} 

示例代碼2

//You have your screen space coordinates as x and y 
    vec3 screenPoint1 = vec3(x, y, 0.f); 
    vec3 screenPoint2 = vec3(x, y, 20.f); 

    //Unproject both these points 
    vec3 modelPoint1 = unProject(screenPoint1, modelWorldMatrix, projectionMatrix, viewPort); 
    vec3 modelPoint2 = unProject(screenPoint2, modelWorldMatrix, projectionMatrix, viewPort); 

    //This is your ray with the two points modelPoint1, modelPoint2 

示例代碼3

//normalOfPlane is the normal of the plane. If it's a xy plane then the normal is vec3(0,0,1) 
    //P0 is a point on the plane 

    //L is the direction of your ray 
    //L0 is a point on the ray 
    vec3 L = modelPoint1 - modelPoint2; 
    vec3 L0 = modelPoint1; 

    //Solve for d where dot((d * L + L0 - P0), n) = 0 
    float d = dot(P0 - L0,normalOfPlane)/dot(L, normalOfPlane); 

    //Use d to get back to point on plane 
    vec3 pointOnPlane = d * L + L0; 

    //Sound of trumpets in the background 
    cout << pointOnPlane.x << " " << pointOnPlane.y << endl; 
+0

你能解釋一下「P0是飛機上的一個點」嗎?另外,我的情況的平面法線是(0,1,0),因爲這些圖塊在XZ座標上是正確的?此外,我的正交投影與您定義的投影不同,它是(-5 *方位,5 *方位,-5,5,0,20)。我的X和Y鼠標座標是否應該適合這個標準? – user975989

+0

飛機上的一個點只是飛機內的一個3d點。瓷磚角落之一將是一個很好的觀點。至於矩陣:沒關係,它只是縮小它有點不同。 – Xonar

相關問題