Обработка сенсорных событий в трехмерной «сцене» или «Экран в трехмерные координаты» - PullRequest
2 голосов
/ 04 ноября 2011

Я пытаюсь реализовать игру типа «бей-моль» с использованием 3D (OpenGL ES) в Android. На данный момент у меня есть ОДНА трехмерная фигура (вращающийся куб) на экране в любой момент времени, которая представляет мою "моль". В моем представлении есть обработчик событий касания, который случайным образом устанавливает некоторые значения x, y в моем рендерере, заставляя куб перемещаться (используя glTranslatef ()).

Мне еще не приходилось сталкиваться с любым учебником или документацией, которые бы полностью связывали события касания экрана с 3D-сценой. Я проделал большую работу, чтобы добраться туда, где я нахожусь, но я не могу понять это до конца.

С developer.andrdoid.com Я использую то, что, как мне кажется, можно считать вспомогательными классами для Матриц: MatrixGrabber.java , MatrixStack.java и MatrixTrackingGL.java .

Я использую эти классы в своем методе GLU.glUnProject , который должен выполнять преобразование из реальных экранных координат в трехмерные или объектные координаты.

Отрывок:

    MatrixGrabber mg = new MatrixGrabber();
    int viewport[] = {0, 0, renderer._width, renderer._height};
    mg.getCurrentModelView(renderer.myg);
    mg.getCurrentProjection(renderer.myg);
    float nearCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    float farCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    float x = event.getX();
    float y = event.getY();
    GLU.gluUnProject(x, y, -1.0f, mg.mModelView, 0, mg.mProjection , 0, viewport, 0, nearCoords, 0)
    GLU.gluUnProject(x, y, 1.0f, mg.mModelView, 0, mg.mProjection , 0, viewport, 0, farCoords, 0)

Этот фрагмент выполняется без ошибок, но вывод не выглядит правильным. Я знаю, что экран имеет происхождение (0,0) в левом нижнем углу. И 3D-сцена, по крайней мере, моя, кажется, имеет происхождение прямо в середине экрана, как классическая декартова система. Поэтому запустите мой код, где координаты экрана (0, 718) касаются нижнего левого угла. Мои выводы из последних параметров в gluUnProject:

Рядом: {-2.544, 2.927, 2.839, 1.99}

Дальний: {0,083, 0,802, -0,760, 0,009}

Эти цифры не имеют никакого смысла для меня. Мое прикосновение даже было в 3-м квадранте, поэтому все мои значения x, y для ближнего и дальнего должны быть отрицательными, но это не так. В документации по gluUnProject не упоминается необходимость преобразования экранных координат. Опять же, та же самая документация заставит вас поверить, что Ближние и Дальние должны были быть массивами размера 3, но они должны быть размером 4, и у меня НЕТ КЛЕЯ, почему.

Итак, у меня есть два вопроса (я уверен, что больше подойдет).

  1. Как я могу убедиться, что получаю правильные ближние и дальние расстояния? координаты на основе координат экрана.
  2. Как только у меня есть ближняя и дальняя координаты, как мне использовать это, чтобы найти, пересекает ли создаваемая ими линия объект на экране.

Ответы [ 2 ]

2 голосов
/ 09 ноября 2011

Я помню, что столкнулся с проблемами с glUnProject на Android еще в студенческие годы. (Это было в первые дни Android) Один из моих сокурсников понял, что наши вычисления будут искажены 4-м измерением в результате glUnProject. Если я правильно помню, это было что-то задокументировано где-то, но по какой-то причине я больше не смог это откопать. Я никогда не копался в его специфике, но, возможно, то, что нам помогло, может быть вам полезно. Это скорее всего связано с математикой, которую мы применили ...

/**
 * Convert the 4D input into 3D space (or something like that, otherwise the gluUnproject values are incorrect)
 * @param v 4D input
 * @return 3D output
 */
private static float[] fixW(float[] v) { 
    float w = v[3];
    for(int i = 0; i < 4; i++) 
        v[i] = v[i] / w;
    return v;
}

Мы фактически использовали вышеупомянутый метод, чтобы исправить результаты glUnProject и выполнить действие выбора / касания / выбора для сферических объектов в трехмерном пространстве. Ниже код может предоставить руководство о том, как это сделать. Это немного больше, чем наведение луча и тест пересечения лучевой сферы.

Несколько дополнительных замечаний, которые могут облегчить понимание приведенного ниже кода:

  • Vector3f - это пользовательская реализация трехмерного вектора, основанная на 3 значениях с плавающей запятой, и реализующая обычные векторные операции.
  • shootTarget - сферический объект в трехмерном пространстве.
  • 0 в вызовах типа getXZBoundsInWorldspace(0) и getPosition(0) - это просто индекс. Мы реализовали анимацию 3D-модели, и индекс определяет, какую «рамку / позу» модели вернуть. Поскольку мы в конечном итоге выполнили этот конкретный тест на попадание на неанимированный объект, мы всегда использовали первый кадр.
  • Concepts.w и Concepts.h - это просто ширина и высота экрана в пикселях - или, может быть, по-другому, для полноэкранного приложения: разрешение экрана.

_

/**
 * Checks if the ray, casted from the pixel touched on-screen, hits
 * the shoot target (a sphere). 
 * @param x
 * @param y
 * @return Whether the target is hit
 */
public static boolean rayHitsTarget(float x, float y) {
    float[] bounds = Level.shootTarget.getXZBoundsInWorldspace(0);
    float radius = (bounds[1] - bounds[0]) / 2f;
    Ray ray = shootRay(x, y);
    float a = ray.direction.dot(ray.direction);  // = 1
    float b = ray.direction.mul(2).dot(ray.near.min(Level.shootTarget.getPosition(0)));
    float c = (ray.near.min(Level.shootTarget.getPosition(0))).dot(ray.near.min(Level.shootTarget.getPosition(0))) - (radius*radius);

    return (((b * b) - (4 * a * c)) >= 0 );

}

/**
 * Casts a ray from screen coordinates x and y.
 * @param x
 * @param y
 * @return Ray fired from screen coordinate (x,y)
 */
public static Ray shootRay(float x, float y){
    float[] resultNear = {0,0,0,1};
    float[] resultFar = {0,0,0,1};

    float[] modelViewMatrix = new float[16];
    Render.viewStack.getMatrix(modelViewMatrix, 0);

    float[] projectionMatrix = new float[16];
    Render.projectionStack.getMatrix(projectionMatrix, 0);

    int[] viewport = { 0, 0, Concepts.w, Concepts.h };

    float x1 = x;
    float y1 = viewport[3] - y;

    GLU.gluUnProject(x1, y1, 0.01f, modelViewMatrix, 0, projectionMatrix, 0, viewport, 0, resultNear, 0);
    GLU.gluUnProject(x1, y1, 50f, modelViewMatrix, 0, projectionMatrix, 0, viewport, 0, resultFar, 0);
    //transform the results from 4d to 3d coordinates.
    resultNear = fixW(resultNear);
    resultFar = fixW(resultFar);
    //create the vector of the ray.
    Vector3f rayDirection = new Vector3f(resultFar[0]-resultNear[0], resultFar[1]-resultNear[1], resultFar[2]-resultNear[2]);
    //normalize the ray.
    rayDirection = rayDirection.normalize();
    return new Ray(rayDirection, resultNear, resultFar);
}

/**
 * @author MH
 * Provides some accessors for a casted ray.
 */
public static class Ray {
    Vector3f direction;
    Vector3f near;
    Vector3f far;

    /**
     * Casts a new ray based on the given direction, near and far params. 
     * @param direction
     * @param near
     * @param far
     */
    public Ray(Vector3f direction, float[] near, float[] far){
        this.direction = direction;
        this.near = new Vector3f(near[0], near[1], near[2]);
        this.far = new Vector3f(far[0], far[1], far[2]);
    }
}
1 голос
/ 05 ноября 2011

Как я могу убедиться, что получаю правильные ближние и дальние координаты на основе координат экрана.

Для начала прочитайте этот ответ , если выне читал информацию о GLU.glProject где-то еще.GLU.glUnProject делает точную инверсию этой функции, которую я нашел более легкой для понимания, и помогает понять концепцию отображения координат экрана в пространство объекта.По крайней мере, сработало для меня.

Если вы хотите проверить, вы получаете правильные значения из GLU.glUnProject, это будет проще всего, если вы начнете с некоторого простого для понимания проецирования и просмотра модели -матрицы.Вот фрагмент кода, который я использовал ранее сегодня:

  // Orthographic projection for the sake of simplicity.
  float projM[] = new float[16];
  Matrix.orthoM(projM, 0, -ratio, ratio, 1f, -1f, zNear, zFar);

  // Model-View matrix is simply a 180 degree rotation around z -axis.
  float mvM[] = new float[16];
  Matrix.setLookAtM(mvM, 0, 0f, 0f, eyeZ, 0f, 0f, 0f, 0f, 1f, 0f);
  Matrix.rotateM(mvM, 0, 180f, 0f, 0f, 1f);

  // (0, 0) is top left point of screen.
  float x = (width - (width / ratio)) / 2;
  float y = height;
  float objNear[] = new float[4];
  float objFar[] = new float[4];
  int view[] = { 0, 0 ,width, height };
  // --> objNear = { 1, 1, eyeZ - zNear  }
  GLU.gluUnProject(x, y, 0, mvM, 0, projM, 0, view, 0, objNear, 0);
  // --> objFar = { 1, 1, eyeZ - zFar }
  GLU.gluUnProject(x, y, 1, mvM, 0, projM, 0, view, 0, objFar, 0);

С более сложными матрицами представления модели и проекции становится довольно сложно проверить, что вы получаете координаты в ожидаемом пространстве объектов.И это может быть хорошей отправной точкой, чтобы сначала поиграться с некоторыми простыми для понимания матрицами.Как только вы довольны результатами, вам не нужно беспокоиться о GLU.glUnProject.

Как только у меня есть ближняя и дальняя координаты, как я могу использовать это, чтобы найти ли линиюони создают пересекающиеся объекты на экране.

Для проверки попадания в трехмерном пространстве объектов должно быть проще всего предварительно вычислить bounding sphere или bounding box для ваших объектов.Как только вы захотите проверить, нажал ли пользователь на объект и имеют две точки экрана unProjected в пространстве объектов, у вас есть две точки на линии в пространстве объектов.

Если вы используете ограничивающую сферу, вы можетевычислите точки пересечения с этой линией или, альтернативно, только центральную точку сферы расстояние .В первом случае должно быть хотя бы одно пересечение, а во втором - расстояние должно быть меньше радиуса сферы.

Для ограничительной рамки этот вопрос - хорошее чтение.

...