Wie bestimmen Sie mit lwjgl, auf welches Objekt / welche Oberfläche der Benutzer zeigt?

9

Der Titel sagt so ziemlich alles. Ich arbeite an einem einfachen Projekt, bei dem wir uns an das Würfeln gewöhnen müssen, bei dem der Zauberwürfel manipuliert wird, und ich kann nicht herausfinden, auf welche Seite / welches Quadrat der Benutzer zeigt.

Flynn1179
quelle
Hinweis: AFAIK viele Engines tun dies vollständig auf der CPU, unabhängig vom Rendern und ohne OpenGL, da es schneller (aber komplizierter) ist.
user253751

Antworten:

8

Sie möchten die 3D-Auswahl verwenden. Hier ist ein Code, den ich in meinem Spiel verwende.

Zuerst warf ich einen Strahl von meiner Kamera. Ich benutze die Maus, aber wenn Sie nur dort verwenden, wo der Benutzer hinschaut, können Sie einfach die Mitte des Fensters verwenden. Dies ist der Code aus meiner Kameraklasse:

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);
}

Dann folge ich dem Strahl, bis er sich mit einem Objekt schneidet. Sie können dies mit Begrenzungsrahmen oder Ähnlichem tun, da dies spezifisch für Ihr Spiel ist. Ich werde Sie damit umgehen lassen. Im Allgemeinen geschieht dies, indem Sie dem Strahl folgen (indem Sie die Richtung des Strahls immer wieder zu seinem Startpunkt hinzufügen, bis Sie auf etwas stoßen).

Als nächstes möchten Sie sehen, welches Gesicht ausgewählt wird. Sie können dies tun, indem Sie über die Dreiecke in Ihrem Würfel iterieren, um zu sehen, ob der Strahl sie schneidet. Die folgende Funktion macht das und gibt den Abstand zum ausgewählten Gesicht zurück. Dann verwende ich nur das geschnittene Gesicht, das der Kamera am nächsten liegt (damit Sie nicht das hintere Gesicht auswählen).

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;
}

Das Dreieck mit dem kürzesten Abstand ist das ausgewählte Dreieck. Außerdem, schamloser Plug für mein Spiel, solltest du es dir ansehen, ihm folgen und in den Umfragen abstimmen, die ich gelegentlich herausbringe. Vielen Dank! http://byte56.com

MichaelHouse
quelle
3

Die Technik, nach der Sie suchen, heißt "Kommissionieren" oder "3D-Kommissionieren". Es gibt verschiedene Möglichkeiten, dies zu tun. Eine der gebräuchlichsten ist die Umwandlung eines 2D-Punkts auf dem Bildschirm in den Augenraum unter Verwendung der Umkehrung der Projektionstransformation. Auf diese Weise können Sie einen Strahl im Ansichtsraum erzeugen, mit dem Sie die Kollision mit der physischen Darstellung Ihrer verschiedenen Teile der Szenengeometrie testen können, um zu bestimmen, welches Objekt der Benutzer "getroffen" hat.

Sie können auch einen "Kommissionierpuffer" (oder "Auswahlpuffer") verwenden, den der GL unterstützt. Dies beinhaltet im Wesentlichen das Schreiben einer eindeutigen Objektkennung in einen Puffer für jedes Pixel und das einfache Testen dieses Puffers.

Die OpenGL-FAQ enthält kurze Erläuterungen zu beiden Themen (sie konzentriert sich mehr auf den Auswahlpuffer, da dies vollständig eine GL-Funktion ist; Ray Picking ist API-unabhängig, außer möglicherweise zum Extrahieren der aktiven Matrizen aus der Pipeline). Hier ist ein spezifischeres Beispiel für die Ray-Picking-Technik (für iOS, aber es sollte leicht genug zu übersetzen sein). Diese Site enthält Quellcode zu einigen der auf LWJGL portierten OpenGL Red Book-Beispiele, einschließlich einer Auswahldemo.

Siehe auch diese Frage zu SO.

Gemeinschaft
quelle
Beachten Sie auch, dass die OpenGL-Kommissionier-API in GL3 Core veraltet ist. Es ist jedoch immer noch im vollständigen Profil verfügbar.
nichtig
Heh, zu wissen, nach welchem ​​Begriff gesucht werden soll, macht einen großen Unterschied :) Allerdings habe ich gerade bei Google nach 'lwjgl picking example' gesucht, und dieser Thread war einer der Top-Hits!
Flynn1179