Bagaimana Anda menentukan objek / permukaan mana yang pengguna tunjuk dengan lwjgl?

9

Judul cukup banyak mengatakan itu semua. Saya sedang mengerjakan proyek sederhana 'mari kita gunakan untuk lwjgl' yang melibatkan manipulasi kubus rubik, dan saya tidak tahu bagaimana cara menentukan sisi / kotak mana yang pengguna tunjuk.

Flynn1179
sumber
Catatan: AFAIK banyak mesin melakukan ini sepenuhnya pada CPU, secara terpisah dari rendering dan tanpa menggunakan OpenGL sama sekali, karena lebih cepat (tetapi lebih rumit).
user253751

Jawaban:

8

Anda akan ingin menggunakan pengambilan 3D. Berikut beberapa kode yang saya gunakan di game saya.

Pertama saya melemparkan sinar dari kamera saya. Saya menggunakan mouse, tetapi jika Anda hanya menggunakan tempat yang dilihat pengguna, Anda bisa menggunakan bagian tengah jendela. Ini adalah kode dari kelas kamera saya:

public Ray GetPickRay() {
    int mouseX = Mouse.getX();
    int mouseY = WORLD.Byte56Game.getHeight() - Mouse.getY();

    float windowWidth = WORLD.Byte56Game.getWidth();
    float windowHeight = WORLD.Byte56Game.getHeight();

    //get the mouse position in screenSpace coords
    double screenSpaceX = ((float) mouseX / (windowWidth / 2) - 1.0f) * aspectRatio;
    double screenSpaceY = (1.0f - (float) mouseY / (windowHeight / 2));

    double viewRatio = Math.tan(((float) Math.PI / (180.f/ViewAngle) / 2.00f)) * zoomFactor;

    screenSpaceX = screenSpaceX * viewRatio;
    screenSpaceY = screenSpaceY * viewRatio;

    //Find the far and near camera spaces
    Vector4f cameraSpaceNear = new Vector4f((float) (screenSpaceX * NearPlane), (float) (screenSpaceY * NearPlane), (float) (-NearPlane), 1);
    Vector4f cameraSpaceFar = new Vector4f((float) (screenSpaceX * FarPlane), (float) (screenSpaceY * FarPlane), (float) (-FarPlane), 1);


    //Unproject the 2D window into 3D to see where in 3D we're actually clicking
    Matrix4f tmpView = Matrix4f(view);
    Matrix4f invView = (Matrix4f) tmpView.invert();
    Vector4f worldSpaceNear = new Vector4f();
    Matrix4f.transform(invView, cameraSpaceNear, worldSpaceNear);

    Vector4f worldSpaceFar = new Vector4f();

    Matrix4f.transform(invView, cameraSpaceFar, worldSpaceFar);

    //calculate the ray position and direction
    Vector3f rayPosition = new Vector3f(worldSpaceNear.x, worldSpaceNear.y, worldSpaceNear.z);
    Vector3f rayDirection = new Vector3f(worldSpaceFar.x - worldSpaceNear.x, worldSpaceFar.y - worldSpaceNear.y, worldSpaceFar.z - worldSpaceNear.z);

    rayDirection.normalise();

    return new Ray(rayPosition, rayDirection);
}

Lalu saya ikuti ray sampai bertemu dengan objek, Anda bisa melakukan ini dengan kotak pembatas atau yang serupa, karena ini khusus untuk gim Anda, saya akan membiarkan Anda mengatasinya. Umumnya ini dilakukan dengan mengikuti sinar keluar (menambahkan arah sinar ke titik mulai berulang-ulang 'sampai Anda menabrak sesuatu).

Selanjutnya Anda ingin melihat wajah mana yang dipetik, Anda bisa melakukannya dengan mengulangi segitiga di kubus Anda untuk melihat apakah sinar memotongnya. Fungsi berikut melakukan itu dan mengembalikan jarak ke wajah yang diambil, maka saya hanya menggunakan wajah berpotongan yang paling dekat dengan kamera (jadi Anda tidak memilih wajah belakang).

public static float RayIntersectsTriangle(Ray R, Vector3f vertex1, Vector3f vertex2, Vector3f vertex3) {
    // Compute vectors along two edges of the triangle.
    Vector3f edge1 = null, edge2 = null;

    edge1 = Vector3f.sub(vertex2, vertex1, edge1);
    edge2 = Vector3f.sub(vertex3, vertex1, edge2);

    // Compute the determinant.
    Vector3f directionCrossEdge2 = null;
    directionCrossEdge2 = Vector3f.cross(R.Direction, edge2, directionCrossEdge2);


    float determinant = Vector3f.dot(directionCrossEdge2, edge1);
    // If the ray and triangle are parallel, there is no collision.
    if (determinant > -.0000001f && determinant < .0000001f) {
        return Float.MAX_VALUE;
    }

    float inverseDeterminant = 1.0f / determinant;

    // Calculate the U parameter of the intersection point.
    Vector3f distanceVector = null;
    distanceVector = Vector3f.sub(R.Position, vertex1, distanceVector);


    float triangleU = Vector3f.dot(directionCrossEdge2, distanceVector);
    triangleU *= inverseDeterminant;

    // Make sure the U is inside the triangle.
    if (triangleU < 0 || triangleU > 1) {
        return Float.MAX_VALUE;
    }

    // Calculate the V parameter of the intersection point.
    Vector3f distanceCrossEdge1 = null;
    distanceCrossEdge1 = Vector3f.cross(distanceVector, edge1, distanceCrossEdge1);


    float triangleV = Vector3f.dot(R.Direction, distanceCrossEdge1);
    triangleV *= inverseDeterminant;

    // Make sure the V is inside the triangle.
    if (triangleV < 0 || triangleU + triangleV > 1) {
        return Float.MAX_VALUE;
    }

    // Get the distance to the face from our ray origin
    float rayDistance = Vector3f.dot(distanceCrossEdge1, edge2);
    rayDistance *= inverseDeterminant;


    // Is the triangle behind us?
    if (rayDistance < 0) {
        rayDistance *= -1;
        return Float.MAX_VALUE;
    }
    return rayDistance;
}

Segitiga dengan jarak terpendek adalah segitiga yang dipetik. Juga, plug tak tahu malu untuk gim saya, Anda harus memeriksanya, mengikutinya, dan memberikan suara di polling yang kadang-kadang saya keluarkan. Terima kasih! http://byte56.com

MichaelHouse
sumber
3

Teknik yang Anda cari disebut "picking" atau "3D picking." Ada beberapa cara untuk melakukannya; salah satu yang lebih umum adalah mengubah titik 2D pada layar menjadi ruang mata dengan menggunakan kebalikan dari transformasi proyeksi. Ini akan memungkinkan Anda untuk menghasilkan sinar dalam ruang tampilan, yang dapat Anda gunakan untuk menguji tabrakan dengan representasi fisik dari berbagai bit geometri pemandangan Anda untuk menentukan objek mana yang terkena 'pengguna'.

Anda juga dapat menggunakan "picking buffer" (atau "buffer pemilihan") yang didukung oleh GL. Ini pada dasarnya melibatkan penulisan beberapa pengidentifikasi objek unik ke dalam buffer untuk setiap piksel dan kemudian cukup menguji buffer itu.

FAQ OpenGL memiliki diskusi singkat tentang keduanya (ini lebih berfokus pada buffer pemilihan karena itu sepenuhnya fitur GL; picking ray adalah agnostik API kecuali mungkin untuk mengekstraksi matriks aktif dari pipa). Berikut adalah contoh yang lebih spesifik dari teknik memetik sinar (untuk iOS, tetapi harus menerjemahkan dengan cukup mudah). Situs ini memiliki beberapa kode sumber ke beberapa contoh Buku Merah OpenGL yang di-porting ke LWJGL, yang mencakup demo pengambilan.

Lihat juga pertanyaan ini di SO.

Komunitas
sumber
Perhatikan juga bahwa API pemungutan OpenGL telah usang dalam GL3 Core. Ini masih tersedia di profil lengkap.
batal
Heh, mengetahui istilah mana yang dicari membuat perbedaan besar :) Namun, saya hanya mencari google untuk 'lwjgl picking example', dan utas ini adalah salah satu hit teratas!
Flynn1179