Создание матрицы преобразования для OpenGL с помощью GLM (вращения) - PullRequest
2 голосов
/ 24 апреля 2020

Исходный вопрос:

Задача

У меня есть единичный куб , который я хотел бы преобразовать так, чтобы он соединял две точки. Я новичок в OpenGL и знаю только самые основные части c частей линейной алгебры. Я попытался имитировать c что-то похожее на полярные координаты в моих попытках соединить точки. Моя текущая реализация не работает, когда есть изменение в Z и другой оси. Я также пытался mat = glm::lookAt(center, terminal, y_axis);, но у меня не было успеха.

Код

Это происходит из тела для l oop, расположенного в schedule_edge_update().

auto const initial = p1;
auto const terminal = p2;
auto const distance = glm::distance(initial, terminal);
auto const length = distance * 0.5f;
auto const center = (initial + terminal) / 2.f;
auto const rejection = terminal - initial;
auto const delta = glm::normalize(rejection);

auto mat = glm::mat4(1);

// translate
mat = glm::translate(mat, center);

// rotate
auto const phi_hyp = glm::length(glm::vec2(delta.x, delta.z));
if (phi_hyp != 0.0f) {
    auto phi = acosf(delta.x / phi_hyp);
    mat = glm::rotate(mat, phi, y_axis);
}

auto const theta_hyp = glm::length(glm::vec2(delta.x, delta.y));
if (theta_hyp != 0.0f) {
    auto theta = acosf(delta.x / theta_hyp);
    theta *= delta.x > 0 ? -1.0f : 1.0f;
    mat = glm::rotate(mat, theta, z_axis);
}

// scale
edges->add_matrix(glm::scale(mat, glm::vec3(length, 0.05f, 0.01f)));

Когда матрица добавляется к edges, она ставится в очередь для буферизации для рендеринга экземпляров.

Далеко

Вот мои контрольные точки и большой куб, который я сделал. Far away

Close Up

Вот пример того, как он не работает. Начальная точка обозначена как p1, а конечная точка - p2. Линия, которая не соединяет какие-либо точки, должна соединять p1 и p2. Close up

Другой крупный план

Вот еще один пример, но у этого есть координаты для p1 и p2, помеченные. p1 и p2 отличаются изменением Y и Z. Однако мой код поворачивает куб (после его перевода) вокруг оси y на 90 градусов. Тогда это весы. Вы можете сказать, что он повернут, потому что он шире на одной из осей (ось Y перед вращением). Different Close Up

Полный список координат

// Test points
auto const A = glm::vec3(-10.0f, -10.0f, -20.0f);
auto const B = glm::vec3(+10.0f, -10.0f, -20.0f);
auto const C = glm::vec3(+10.0f, +10.0f, -20.0f);
auto const D = glm::vec3(+00.0f, +10.0f, -20.0f);
auto const E = glm::vec3(+05.0f, +05.0f, -20.0f);
auto const F = glm::vec3(+00.0f, +00.0f, -30.0f);
auto const G = glm::vec3(-10.0f, -10.0f, -30.0f);
auto const H = glm::vec3(+55.0f, -15.0f, -60.0f);
auto const I = glm::vec3(+55.0f, -05.0f, -70.0f);

get_nodes().emplace_back(A);
get_nodes().emplace_back(B);
get_nodes().emplace_back(C);
get_nodes().emplace_back(D);
get_nodes().emplace_back(E);
get_nodes().emplace_back(F);
get_nodes().emplace_back(G);
get_nodes().emplace_back(H);
get_nodes().emplace_back(I);

get_edges().emplace_back(A, B);
get_edges().emplace_back(B, C);
get_edges().emplace_back(C, D);
get_edges().emplace_back(D, E);
get_edges().emplace_back(E, F);
get_edges().emplace_back(F, G);
get_edges().emplace_back(G, H);
get_edges().emplace_back(H, I);

// Big cube
auto const C0 = glm::vec3(-5.0f, -5.0f, -5.0f);
auto const C1 = glm::vec3(-5.0f, -5.0f, +5.0f);
auto const C2 = glm::vec3(-5.0f, +5.0f, -5.0f);
auto const C3 = glm::vec3(-5.0f, +5.0f, +5.0f);
auto const C4 = glm::vec3(+5.0f, -5.0f, -5.0f);
auto const C5 = glm::vec3(+5.0f, -5.0f, +5.0f);
auto const C6 = glm::vec3(+5.0f, +5.0f, -5.0f);
auto const C7 = glm::vec3(+5.0f, +5.0f, +5.0f);

get_nodes().emplace_back(C0);
get_nodes().emplace_back(C1);
get_nodes().emplace_back(C2);
get_nodes().emplace_back(C3);
get_nodes().emplace_back(C4);
get_nodes().emplace_back(C5);
get_nodes().emplace_back(C6);
get_nodes().emplace_back(C7);

get_edges().emplace_back(C0, C1);
get_edges().emplace_back(C0, C2);
get_edges().emplace_back(C0, C4);
get_edges().emplace_back(C1, C3);
get_edges().emplace_back(C1, C5);
get_edges().emplace_back(C2, C3);
get_edges().emplace_back(C2, C6);
get_edges().emplace_back(C3, C7);
get_edges().emplace_back(C4, C5);
get_edges().emplace_back(C4, C6);
get_edges().emplace_back(C5, C7);
get_edges().emplace_back(C6, C7);

schedule_node_update();
schedule_edge_update();

Решение Spektre с использованием GLM

Код

auto constexpr A = vec3(-0.5f, 0.0f, 0.0f);
auto constexpr B = vec3(+0.5f, 0.0f, 0.0f);
auto const C = p1;
auto const D = p2;

auto M = mat4(1.0f);

// Translate
auto const center = 0.5 * (C + D);
M = translate(M, center);

// Rotate
auto constexpr p = B - A;
auto const q = D - C;
auto const n = cross(p, q);
if (n != vec3()) {
    auto const a = angle(normalize(p), normalize(q));
    M = rotate(M, a, n);
}

// Scale
auto constexpr thickness = 0.05f;
M = scale(M, vec3(0.5f * distance(C, D), thickness, thickness));

edges->add_matrix(M);

Успешный результат

Successful Result

1 Ответ

3 голосов
/ 24 апреля 2020

Итак, проблема сводится к следующему:

Я знаю 4 балла A,B,C,D и хочу вычислить матрицу преобразования, которая преобразует A,B в C,D.

overview

Это можно сделать так. Предположим, мы конвертируем точки следующим образом:

M * A = C
M * B = D

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

Если вы рассекаете M немного , это просто вопрос знания положения, ориентация и масштаб.

  1. Масштаб - это самое простое

    , это просто отношение длины линии после и до преобразования.

    scale = |CD|/|AB|
    
  2. ориентация

    это представлено единицами базисных векторов. Мы можем использовать тот факт, что AB и CD имеют только одно вращение (все остальные просто создают бесконечное число решений), поэтому мы можем просто повернуть AB на угол между AB, CD вокруг оси, перпендикулярной обоим AB CD. Угол, который мы можем получить через ако точечного произведения между единичными векторами, параллельными AB, CD. Единственная проблема заключается в том, что мы не будем указывать направление вращения, поэтому нам нужно проверить две возможности (CW, CCW).

    так:

     axis  = cross(B-A,D-C)
     angle = +/- acos(dot(B-A,D-C) / |B-A|*|D-C|)
    
  3. перевод

    это просто, мы просто преобразуем A с M без перевода, давайте назовем его A', а затем просто исправим полученную позицию, чтобы она перешла на C.

    M_origin += C-A'
    

    Помните, что перевод должен быть установлен напрямую, без применения матрицы перевода. Они обычно переводятся в локальную систему координат [LCS], которая сначала преобразует разницу в нее. В таком случае используйте

    translate(Inverse(M)*(C-A'))
    

    или

    translate(M*(C-A'))
    

    в зависимости от используемых обозначений.

Здесь маленький C ++ / VCL / old GL пример:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
#include "OpenGLrep4d_double.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
double arot=0.0;                // just animation angle
//---------------------------------------------------------------------------
const int pnts=8;
double pnt[pnts*3]=             // Vertexes for 10x10x10 cube centered at (0,0,0)
    {
    -5.0,-5.0,-5.0,
    -5.0,+5.0,-5.0,
    +5.0,+5.0,-5.0,
    +5.0,-5.0,-5.0,
    -5.0,-5.0,+5.0,
    -5.0,+5.0,+5.0,
    +5.0,+5.0,+5.0,
    +5.0,-5.0,+5.0,
    };
const int lins=12;
int lin[lins*2]=                // lines (index of point used) no winding rule
    {
    0,1,1,2,2,3,3,0,
    4,5,5,6,6,7,7,4,
    0,4,1,5,2,6,3,7,
    };
double A[3]={-5.0,-5.0,-5.0};   // cube diagonal
double B[3]={+5.0,+5.0,+5.0};
double C[3]={-4.5, 2.0, 0.0};   // wanted cube diagonal
double D[3]={+4.5, 5.0, 0.0};
double M[16];                   // our transform matrix
//---------------------------------------------------------------------------
void compute_M()
    {
    double scale,p[3],q[3],n[3],a;
    const double deg=180.0/M_PI;
    const double rad=M_PI/180.0;
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // scale
    vector_sub(p,B,A);                      // p=B-A
    vector_sub(q,D,C);                      // q=D-C
    scale=vector_len(q)/vector_len(p);      //  =|q|/|p|

    // rotation between AB and CD
    vector_mul(n,p,q);                      // n = (p x q) ... cross product
    vector_one(p,p);                        // p = p/|p|
    vector_one(q,q);                        // q = q/|q|
    a=acos(vector_mul(p,q));                // angle between AB and CD in [rad]

    glLoadIdentity();                       // unit matrix
    glRotated(+a*deg,n[0],n[1],n[2]);       // rotate by angle around normal to AB,CD
    glScaled(scale,scale,scale);            // apply scale
    glGetDoublev(GL_MODELVIEW_MATRIX,M);    // get the M from OpenGL

    // translation
    matrix_mul_vector(p,M,A);               // p = M*A
    vector_sub(p,C,p);                      // p = C-p
    M[12]=p[0];
    M[13]=p[1];
    M[14]=p[2];
    M[15]=1.0;

    // verify
    matrix_mul_vector(p,M,B);               // p = M*B
    vector_sub(p,p,D);                      // p = p-C
    if (vector_len(p)>1e-3)                 // if |p| too big use other direction to rotate
        {
        glLoadIdentity();                       // unit matrix
        glRotated(-a*deg,n[0],n[1],n[2]);       // rotate by angle around normal to AB,CD
        glScaled(scale,scale,scale);            // apply scale
        glGetDoublev(GL_MODELVIEW_MATRIX,M);    // get the M from OpenGL
        }

    glPopMatrix();
    }
//---------------------------------------------------------------------------
void gl_draw()      // main rendering code
    {
    int i;
    double m0[16],m1[16],m[16],x[3],y[3],z[3],t2[3][3];

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0.0,0.0,-50.0);
    glRotated(15.0,1.0,0.0,0.0);
    glRotated(arot,0.0,1.0,0.0);

    glBegin(GL_LINES);
    glColor3f(1.0,0.0,0.0); for (i=0;i<lins*2;i++) glVertex3dv(pnt+(lin[i]*3)); // render original cube
    glColor3f(0.0,1.0,0.0); glVertex3dv(A); glVertex3dv(B);                     // render original diagonal AB
    glColor3f(1.0,1.0,0.0); glVertex3dv(C); glVertex3dv(D);                     // render wanted diagonal CD
    glEnd();

    // render transformed cube
    glMatrixMode(GL_MODELVIEW);
    glMultMatrixd(M);
    glBegin(GL_LINES);
    glColor3f(0.0,0.0,1.0); for (i=0;i<lins*2;i++) glVertex3dv(pnt+(lin[i]*3)); // render transformed cube
    glEnd();


    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    // application init
    gl_init(Handle);
    compute_M();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
    // application exit
    gl_exit();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    // window resize
    gl_resize(ClientWidth,ClientHeight);
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    {
    // window repaint
    gl_draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
    arot+=1.5; if (arot>=360.0) arot-=360.0;
    gl_draw();
    }
//---------------------------------------------------------------------------

Просто игнорируйте материал, связанный с VCL. Функции поддержки GL вы можете найти здесь:

Единственное важное здесь - compute_M() вместе с глобальными переменными.

Векторные математические функции комментируются (так что вы можете перевести это в GLM), если вам нужны реализации, вы можете найти их в связанном QA выше. Это в основном занимает. Для простоты я использовал собственные вращения GL (будьте осторожны, они в градусах, а не в радианах).

Вот превью:

preview

  • red является оригинальным кубом
  • green является оригинальным диагональ AB
  • blue преобразуется кубом M
  • yellow - это искомая диагональ CD

Как видно, он соответствует .

Если вам нужно выровнять больше, чем просто линию, вам нужно добавить больше информации для выравнивания (например, 2 линии (3 точки)) et c. Для получения дополнительной информации см .:

...