Как преобразовать координату мыши на экране в 3D-координату

Я создаю 3D-приложение, используя GLUT на С++.

Теперь я хочу реализовать метод, подобный этому:

Vector3* MyClass::get3DObjectfromMouse(int mouseX, int mouseY);

Как я могу реализовать этот метод?


person user1956702    schedule 14.05.2014    source источник
comment
Как насчет отбрасывания луча по экранной координате и выбора первого объекта, пересекаемого этим лучом?   -  person vadimvolk    schedule 14.05.2014
comment
Вы не можете, вам нужна 3-я координата, чтобы сделать это. Обычно люди используют ближнюю плоскость для точки 1, дальнюю плоскость для точки 2, а затем проводят луч, проходящий через обе точки. Этот луч представляет собой бесконечное число точек, которые проецируются на Window《x,y》. В качестве альтернативы, если вам нужна позиция объекта на экране в 《x,y》, вы можете прочитать буфер глубины, что даст вам Window《z》, необходимое для получения непроецируемой позиции.   -  person Andon M. Coleman    schedule 14.05.2014


Ответы (1)


Как прокомментировал Andon M. Коулман, один из способов добиться этого — выполнить тест на пересечение луча и объекта с непроецируемыми экранными координатами. Этот метод широко известен как пикинг.

Псевдо-C++ код для выбора:

Предположим, у нас есть тип/класс 3D-объекта:

class Object3D { ... };

Трехмерная функция выбора вернет список всех объектов, пересекаемых линией, идущей от заданной двухмерной точки в ближней плоскости к той же точке в дальней плоскости.

struct LineSegment 
{
    Vector3 start;
    Vector3 end;
};

Object3D[] Pick(float x, float y)
{
    LineSegment lineSeg;
    Object3D[] intersectedObjs;

    // Do both un-projections for z-near (0) and z-far (1).
    // This produces a line segment going from z-near to far.
    UnProject(x, y, /* z = */ 0.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.start);
    UnProject(x, y, /* z = */ 1.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.end);

    // Iterate all object in the scene or in the current view:
    for (Object3D obj : scene)
    {
        if (TestLineIntersection(obj, lineSeg))
        {
            // This object is crossed by the picking line.
            intersectedObjs.Add(obj);
        }
    }

    // Optionally you might want sort them from distance 
    // to the camera/viewer before returning the intersections.
    return intersectedObjs;
}

И функция UnProject() будет выглядеть так:

bool UnProject(float winX, float winY, float winZ,
               const Matrix4 & modelView, const Matrix4 & projection,
               const ScreenRect viewport, Vector3 & worldCoordinates)
{
    // Compute (projection x modelView) ^ -1:
    const Matrix4 m = inverse(projection * modelView);

    // Need to invert Y since screen Y-origin point down,
    // while 3D Y-origin points up (this is an OpenGL only requirement):
    winY = viewport.Height() - winY;

    // Transformation of normalized coordinates between -1 and 1:
    Vector4 in;
    in[0] = (winX - viewport.X()) / viewport.Width()  * 2.0 - 1.0;
    in[1] = (winY - viewport.Y()) / viewport.Height() * 2.0 - 1.0;
    in[2] = 2.0 * winZ - 1.0;
    in[3] = 1.0;

    // To world coordinates:
    Vector4 out(m * in);
    if (out[3] == 0.0) // Avoid a division by zero
    {
        worldCoordinates = Vector3Zero;
        return false;
    }

    out[3] = 1.0 / out[3];
    worldCoordinates[0] = out[0] * out[3];
    worldCoordinates[1] = out[1] * out[3];
    worldCoordinates[2] = out[2] * out[3];
    return true;
}

Чтобы уточнить, TestLineIntersection() выполняет тест пересечения линии и AABB. Ограничивающая рамка должна быть преобразована в мировое пространство, поскольку она обычно выражается в виде набора точек в локальном пространстве модели.

bool TestLineIntersection(const Object3D & obj, const LineSegment & lineSeg)
{
    AABB aabb = obj.GetAABB();
    aabb.TransformBy(obj.modelMatrix);
    return aabb.LineIntersection(lineSeg.start, lineSeg.end);
}

// AABB.cpp:
bool AABB::LineIntersection(const Vector3 & start, const Vector3 & end) const
{
    const Vector3 center     = (mins + maxs) * 0.5;
    const Vector3 extents    = maxs - center;
    const Vector3 lineDir    = 0.5 * (end - start);
    const Vector3 lineCenter = start + lineDir;
    const Vector3 dir        = lineCenter - center;

    const float ld0 = Mathf::Abs(lineDir[0]);
    if (Mathf::Abs(dir[0]) > (extents[0] + ld0))
    {
        return false;
    }

    const float ld1 = Mathf::Abs(lineDir[1]);
    if (Mathf::Abs(dir[1]) > (extents[1] + ld1))
    {
        return false;
    }

    const float ld2 = Mathf::Abs(lineDir[2]);
    if (Mathf::Abs(dir[2]) > (extents[2] + ld2))
    {
        return false;
    }

    const Vector3 vCross = cross(lineDir, dir);
    if (Mathf::Abs(vCross[0]) > (extents[1] * ld2 + extents[2] * ld1))
    {
        return false;
    }
    if (Mathf::Abs(vCross[1]) > (extents[0] * ld2 + extents[2] * ld0))
    {
        return false;
    }
    if (Mathf::Abs(vCross[2]) > (extents[0] * ld1 + extents[1] * ld0))
    {
        return false;
    }

    return true;
}
person glampert    schedule 14.05.2014
comment
-1 Этот вопрос не заслуживает такого красивого ответа. ;) - person Qix - MONICA WAS MISTREATED; 14.05.2014
comment
Кстати, я только что реализовал это несколько дней назад, так что это было еще свежо в моей голове :) - person glampert; 14.05.2014
comment
@гламперт. Не могли бы вы поделиться фрагментом для TestLineIntersection? Спасибо! - person AlvinfromDiaspar; 11.06.2014
comment
Никаких проблем @AlvinfromDiaspar, добавлено! Кроме того, я бы порекомендовал вам взглянуть на математические определения пересечения линии и прямоугольника. Это поможет вам лучше понять, что происходит в коде. - person glampert; 11.06.2014
comment
Извините, а как насчет TransformBy? Кроме того, могут ли значения ближнего и дальнего окна просмотра быть (0,1 и 100)? - person AlvinfromDiaspar; 12.06.2014
comment
Кстати, я просто хочу пояснить, что делает метод Unproject. Насколько я понимаю, он возвращает вершину в мировых координатах. Если это правда, то как на это влияют мировые координаты камеры? Я спрашиваю об этом, потому что мои начальный и конечный векторы кажутся относительно начала мира (а не моей камеры). - person AlvinfromDiaspar; 13.06.2014
comment
Функция TransformBy просто выполняет стандартное преобразование точек с помощью modelMatrix для минимальной и максимальной точек AABB. Что касается Unproject, то результат в мировых координатах. Я рекомендую вам взглянуть на следующую ссылку, она объясняет старую функцию gluUnProject(), которая делает то же самое: myweb.lmu.edu/dondi/share/cg/unproject-explained.pdf - person glampert; 13.06.2014
comment
Я просто немного сбит с толку, если мне нужно учитывать положение моей камеры при расчете преобразования экрана в мир. Ссылка: gamedev.stackexchange.com/questions/ 6940/ - person AlvinfromDiaspar; 13.06.2014
comment
Просто чтобы добавить ко мне предыдущий комментарий / вопрос, я получаю ближний и дальний векторы, где координаты x, y равны › -1 и ‹ 1. Следует ли это умножать на координаты x, y камеры? - person AlvinfromDiaspar; 13.06.2014
comment
Вы должны передать матрицы вида и проекции вашей камеры в функцию Unproject(). Это уже будет учитывать положение камеры. Возвращаемые точки для 0 и 1 Z — это две точки мирового пространства. Если вы попытаетесь провести с ними линию, что я рекомендую вам сделать, вы увидите, что линия идет от точки, в которой находится камера, до самого дальнего Z вашей сцены. - person glampert; 13.06.2014
comment
Но мои координаты x, y из моих вызовов unproject находятся между -1 и 1. Так что, может быть, мой unproject неверен? Я знаю, что это должно быть неправильно, потому что я нажимаю на объекты, которые, как я знаю, имеют большие значения координаты x. - person AlvinfromDiaspar; 14.06.2014
comment
Трудно сказать. Я думаю, будет лучше, если вы зададите вопрос здесь или в разделе разработчиков игр и предоставите немного больше контекста. - person glampert; 14.06.2014
comment
Вот мой опубликованный вопрос: stackoverflow.com/questions/24221764 / - person AlvinfromDiaspar; 16.06.2014
comment
Быстрый вопрос относительно viewport.X() и viewport.Y(). Разве они не всегда будут 0? Если нет, то когда могут быть значения X и Y > 0? - person AlvinfromDiaspar; 16.06.2014
comment
Если рендерить с начала экрана, то да. Теоретически вы можете отображать часть экрана по любым причинам. В таком случае x,y будет › 0. - person glampert; 16.06.2014