¿Cómo se determina a qué objeto / superficie apunta el usuario con lwjgl?

9

El título lo dice todo. Estoy trabajando en un proyecto simple de 'vamos a acostumbrarnos a lwjgl' que involucra la manipulación del cubo de rubik, y no puedo entender cómo saber a qué lado / cuadrado apunta el usuario.

Flynn1179
fuente
Nota: AFAIK muchos motores hacen esto completamente en la CPU, por separado del renderizado y sin usar OpenGL en absoluto, porque es más rápido (pero más complicado).
user253751

Respuestas:

8

Querrás usar la selección 3D. Aquí hay un código que uso en mi juego.

Primero lanzo un rayo desde mi cámara. Estoy usando el mouse, pero si solo estás usando donde está mirando el usuario, puedes usar el centro de la ventana. Este es el código de mi clase de cámara:

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

Luego sigo el rayo hasta que se cruza con un objeto, puedes hacer esto con cuadros delimitadores o algo similar, ya que esto es específico de tu juego, te dejaré manejar eso. En general, esto se hace siguiendo el rayo (agregando la dirección del rayo a su punto de partida una y otra vez hasta que te topas con algo).

A continuación, desea ver qué cara se está seleccionando, puede hacerlo iterando sobre los triángulos en su cubo para ver si el rayo los intersecta. La siguiente función hace eso y devuelve la distancia a la cara seleccionada, luego solo uso la cara intersecada que está más cerca de la cámara (por lo que no está eligiendo la cara posterior).

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

El triángulo con la distancia más corta es el triángulo elegido. Además, un complemento descarado para mi juego, deberías echarle un vistazo, seguirlo y votar en las encuestas que publico ocasionalmente. ¡Gracias! http://byte56.com

MichaelHouse
fuente
3

La técnica que está buscando se llama "selección" o "selección 3D". Hay varias formas de hacerlo; Uno de los más comunes es transformar un punto 2D en la pantalla en el espacio ocular utilizando el inverso de la transformación de proyección. Esto le permitirá generar un rayo en el espacio de visualización, que puede usar para probar la colisión con la representación física de sus diversos fragmentos de geometría de escena para determinar qué objeto 'golpeó' el usuario.

También puede usar un "buffer de selección" (o "buffer de selección") para el cual GL tiene soporte. Básicamente, esto implica escribir un identificador de objeto único en un búfer para cada píxel y luego simplemente probar ese búfer.

Las preguntas frecuentes de OpenGL tienen breves discusiones sobre ambos (se enfoca más en el búfer de selección, ya que es completamente una función GL; la selección de rayos es independiente de la API, excepto quizás para extraer las matrices activas de la tubería). Aquí hay un ejemplo más específico de la técnica de selección de rayos (para iOS, pero debería traducirse con la suficiente facilidad). Este sitio tiene algún código fuente para algunos de los ejemplos del Libro Rojo de OpenGL portados a LWJGL, que incluyen una demostración de selección.

Ver también esta pregunta sobre SO.

Comunidad
fuente
También tenga en cuenta que la API de selección de OpenGL ha quedado en desuso en GL3 Core. Sin embargo, todavía está disponible en el perfil completo.
nulo
Je, saber qué término buscar hace una gran diferencia :) Sin embargo, solo busqué en Google 'lwjgl picking example', ¡y este hilo fue uno de los mejores éxitos!
Flynn1179