Как показать видимую часть плоского мира, визуализированную в трехмерной перспективе на двухмерной мини-карте наверху? - PullRequest
0 голосов
/ 24 сентября 2018

Пролог

Этот Q & A является ремейком:

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

Вопрос

Давайте предположим, что наш мироднородная прямоугольная квадратная сетка (представленная двумерным массивом плиток), отображаемая на плоскость (скажем, плоскость XY (Z=0.0) для простоты), и визуализируется с перспективной проекцией.как это:

preview

Как сопоставить перспективу (видимая часть карты / плоскости) с многоугольной формой красного цвета на миникарте?

Чтобы быть более универсальным, предположим, что это входные данные:

  • плоскость (Z=0.0), определенная как начальная точка p0 и два базисных вектора du,dv, которые отображают массив 2D-картплиток в 3D ...
  • ModelView матрица и Perspective используемая матрица

И требуемый результат:

  • 4-точечный многоугольник (включенминикарта), представляющая видимую часть нашей плоскости

Ограничения (чтобы более или менее соответствовать исходному вопросу):

  • use C ++
  • старый стиль OpenGL ( GL, GLU )
  • нет 3-й партии lib для векторной / матричной математики

1 Ответ

0 голосов
/ 24 сентября 2018

Итак, мы хотим получить 4 точки пересечения между нашей плоскостью (Z=0.0) и камерой Frustrum.Таким образом, идея состоит в том, чтобы отбросить 4 луча (по одному на каждый край борозды) из фокуса камеры и просто вычислить пересечение луча / плоскости.Так как плоскость равна Z=0.0, точка пересечения тоже имеет Z=0.0, поэтому пересечение довольно легко вычислить.

  1. Приведение луча для каждого угла / ребра

    от фокуса камеры до угла экрана (в пространстве экрана)

    frustrum

    и преобразовать его в глобальные глобальные координаты (путем обращения перспективы ис использованием обратной матрицы моделей это описано позже).Луч должен быть в форме:

    p(t) = p + dp*t
    

    , где p - фокус, а dp - вектор направления (не нуждается в нормализации)

  2. вычислить пересечение с плоскостью XY (Z=0.0)

    Как z=0.0, тогда:

    0 = p.z + dp.z*t
    t = -p.z/dp.z
    

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

  3. преобразовать трехмерные точки пересечения в u,v внутри карты

    для этого простого точечного произведения достаточно.Так что, если p является нашей точкой пересечения, то:

    u = dot(p-p0,du)
    v = dot(p-p0,dv)
    

    , где u,v - это координаты в нашем массиве 2D-карт или миникарте.Если ваши u,v выровнены по оси, вы можете напрямую использовать (p.x-p0.x,p.y-p0.y) без точечного произведения

Как преобразовать точку p из координат камеры в глобальные мировые координаты:

  1. вернуть перспективу

    сначала получить параметры матрицы перспективы

    double per[16],zNear,zFar,fx,fy;
    glGetDoublev(GL_PROJECTION_MATRIX,per);
    zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
    zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
    fx=per[0];
    fy=per[5];
    

    Это даст вам проблемыближняя и дальняя плоскости и масштабирование по осям x, y.Теперь обратная перспектива - это просто инвертирование деления перспективы следующим образом:

    p[1]*=(-p[2]/fy);                   // apply inverse of perspective
    p[0]*=(-p[2]/fx);
    

    znear и zfar необходимы для наведения лучей.Для получения дополнительной информации см .:

  2. глобальные мировые координаты

    просто используйте обратную матрицу ModelView на нашем p.Итак, сначала получите матрицу:

    double cam[16];
    glGetDoublev(GL_MODELVIEW_MATRIX,cam);
    

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

    p = Inverse(cam)*p;
    

    , но не забывайте, что p должно быть однородным, поэтому (x,y,z,1) для точек и (x,y,z,0) для векторов.

Посмотрите здесь, если вам не хватает базовых знаний или вам нужна математика вектор / матрица:

Здесь Small C ++ пример этого:

//---------------------------------------------------------------------------
void matrix_mul_vector(double *c,double *a,double *b)
    {
    double q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
void  matrix_inv(double *a,double *b) // a[16] = Inverse(b[16])
    {
    double x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }
//---------------------------------------------------------------------------
void draw_map()
    {
    int i,j;
    double u,v,p[3],dp[3];

    // here 3D view must be already set (modelview,projection)
    glDisable(GL_CULL_FACE);

    // [draw 3D map]
    const int n=30;                 // map size
    double p0[3]={0.0,0.0,0.0};     // map start point
    double du[3]={1.0,0.0,0.0};     // map u step (size of grid = 1.0 )
    double dv[3]={0.0,1.0,0.0};     // map v step (size of grid = 1.0 )
    glColor3f(0.5,0.7,1.0);
    glBegin(GL_LINES);
    for (j=0;j<=n;j++)
        {
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(0)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(n)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(0)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(n)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        }
    glEnd();

    // [compute trapeze points]
    double cam[16],per[16],pt[4][3],zNear,zFar,fx,fy;
    glGetDoublev(GL_PROJECTION_MATRIX,per); // obtain matrices
    glGetDoublev(GL_MODELVIEW_MATRIX,cam);
    matrix_inv(cam,cam);
    zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
    zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
    fx=per[0];
    fy=per[5];
    for (j=0;j<4;j++)                       // 4 corners
        {
        for (i=0;i<3;i++) dp[i]=0.0;        // cast ray from camera focus dp
        if (j==0) { p[0]=-1.0; p[1]=-1.0; } // to screen corner p
        if (j==1) { p[0]=-1.0; p[1]=+1.0; }
        if (j==2) { p[0]=+1.0; p[1]=+1.0; }
        if (j==3) { p[0]=+1.0; p[1]=-1.0; }
        p[2]=zNear;                         // start position at screen plane
        p[1]*=(-p[2]/fy);                   // apply inverse of perspective
        p[0]*=(-p[2]/fx);
        // transform to worlds global coordinates
        matrix_mul_vector( p,cam, p);
        matrix_mul_vector(dp,cam,dp);
        // compute intersection of ray and XY plane (z=0) as pt[j] (i exploited the fact that the intersection have z=0.0 for arbitrary plane it would be a bit more complicated)
        for (i=0;i<3;i++) dp[i]=p[i]-dp[i];
        u=p[2]/dp[2];
        if (u<0.0) u=(p[2]-zFar)/dp[2];     // no intersection means "infinite" visibility
        for (i=0;i<3;i++) pt[j][i]=p[i]-(u*dp[i]);
        u=0.0;
        }

    // [draw 2D minimap]
    GLint vp0[4];
    GLint vp1[4]={10,10,150,150};           // minimap position and size ppixels[
    double q0[2]={-1.0,-1.0 };              // minimap start point
    double eu[2]={2.0/double(n),0.0};       // minimap u step
    double ev[2]={0.0,2.0/double(n)};       // minimap v step

    // set 2D view for minimap
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glGetIntegerv(GL_VIEWPORT,vp0);
    glViewport(vp1[0],vp1[1],vp1[2],vp1[3]);

    glColor3f(0.0,0.0,0.0);                 // clear background
    glBegin(GL_QUADS);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    glEnd();

    glColor3f(0.15,0.15,0.15);              // grid
    glBegin(GL_LINES);
    for (j=0;j<=n;j++)
        {
        for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
        }
    glEnd();

    glColor3f(0.5,0.5,0.5);                 // border of minimap
    glLineWidth(2.0);
    glBegin(GL_LINE_LOOP);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    glEnd();
    glLineWidth(1.0);

    // 2D minimap render of the pt[]
    glColor3f(0.7,0.1,0.1);                 // trapeze
    glBegin(GL_LINE_LOOP);
    for (j=0;j<4;j++)
        {
        // get u,v from pt[j]
        for (i=0;i<3;i++) p[i]=pt[j][i]-p0[i];
        for (u=0.0,i=0;i<3;i++) u+=p[i]*du[i];
        for (v=0.0,i=0;i<3;i++) v+=p[i]*dv[i];
        // convert to 2D position and render
        for (i=0;i<2;i++) p[i]=q0[i]+(u*eu[i])+(v*ev[i]); glVertex2dv(p);
        }
    glEnd();

    // restore 3D view
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glViewport(vp0[0],vp0[1],vp0[2],vp0[3]);
    glEnable(GL_DEPTH_TEST);
    }
//---------------------------------------------------------------------------

И предварительный просмотр:

preview

Как видите, для этого нам нужны только матричные * векторные умножения и псевдообратные матричные функции (все остальные, такие как dot,+,-, действительно просты и напрямуюзакодированы как встроенный код), и оба достаточно просты, чтобы напрямую реализовать его в коде, поэтому нет необходимости в GLM или аналогичной библиотеке.

Также мне было лень обрезать 4-точечный многоугольник на миникартеразмер, поэтому вместо этого я использовал glViewport, который сделал это для меня.

Здесь Win32 BDS2006VCL / C ++ / OpenGL1.0 Демонстрация:

Просто выберите медленная загрузка и введите код подтверждения с картинки.Он не использует сторонних библиотек, кроме GL, GLU .Камера статична, поэтому просто добавьте события клавиатуры / мыши по своему вкусу.Если вы хотите перенести это в вашу среду, просто имитируйте поведение событий и игнорируйте материал VCL .

Инициализация OpenGL выполняется на основе этого:

Я только что удалил из него GLEW, GLSL и VAO .

...