Создание матрицы преобразования для OpenGL с GLM (вращения)

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

Проблема

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

Код

Это происходит из тела цикла for, расположенного в 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, она ставится в очередь для буферизации для инстансного рендеринга.

Далеко

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

Закрыть вверх

Вот пример того, что это не работает. Начальная точка обозначена p1, а конечная точка - p2. Линия, не соединяющая никаких точек, должна соединять точки p1 и p2. Крупный план

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

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

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

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

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

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


person user1032677    schedule 24.04.2020    source источник
comment
Непонятно, чего ты хочешь добиться! Что вы имеете в виду, соединяя 2 точки по матрице? какие точки? Вы, возможно, строите куб аналогично графике черепахи? Матрица не соединяется ни с чем, она может позиционировать, ориентировать и масштабировать некоторую геометрию. Я не вижу соответствующего кода в вашем образце, нет рендеринга, я не вижу геометрии, я не вижу управляющего кода (который объяснил бы, что вы пытаетесь сделать). Пожалуйста, отредактируйте свой вопрос немного, чтобы было понятнее ....   -  person Spektre    schedule 24.04.2020
comment
@Spektre Я пытаюсь переместить единичный куб из исходной точки между двумя узлами. Затем поверните и масштабируйте его так, чтобы он соединил узлы. imgur.com/a/U2nxItB Я могу связать репо, но контрольный код довольно велик. Куб и сфера загружаются через файлы .obj.   -  person user1032677    schedule 24.04.2020
comment
Итак, вы хотите, чтобы диагональ вашего куба начиналась / заканчивалась на 2 точках? Знаете ли вы центр куба, размер, ориентацию (в идеале положение двух диагональных точек)? а также 2 очка, по которым вы хотите, чтобы куб ударил?   -  person Spektre    schedule 24.04.2020
comment
@Spektre Да, куб начинается как единичный куб в начале координат. Я также знаю, где находятся точки p1 и p2, и могу перевести куб в их центр и масштабировать куб до идеальной длины, чтобы коснуться их, если он был повернут правильно.   -  person user1032677    schedule 24.04.2020
comment
таким образом, выровненный по оси куб равен 1. с центром вокруг (0,0,0) и длиной стороны 1 или 2 или 2. (0,0,0) - одна диагональная точка, а (1,1,1) - другая? Ваше изображение предполагает последнее.   -  person Spektre    schedule 24.04.2020
comment
@Spektre Большой куб на картинке размером 10x10x10 с центром в (0,0,00. Я добавил все координаты для каждой точки внизу сообщения.   -  person user1032677    schedule 24.04.2020
comment
попробую привести какой-нибудь пример ... но это займет у меня некоторое время ... Также я не использую GLM, поэтому вам нужно будет преобразовать векторную математику в имена функций GLM. также из точек, ваш куб не центрирован на (0,0,0), но, поскольку я получил диагонали, которых достаточно ...   -  person Spektre    schedule 24.04.2020
comment
@Spektre Почему большой куб (C0-C7, нижняя половина списка координат) не центрирован в (0, 0, 0)?   -  person user1032677    schedule 24.04.2020
comment
Ой, я видел ABCDEFGH ... так что C? - это куб, о котором идет речь ...   -  person Spektre    schedule 24.04.2020
comment
Успеваю закончить редактирование вижу свой ответ.   -  person Spektre    schedule 24.04.2020
comment
@Spektre, Сработало как шарм. Единственное изменение, которое я сделал, - это пропустить вращение, если нормаль была равна нулю. Если бы вы могли опубликовать что-нибудь через здесь, я бы хотел принять этот ответ в математическом стеке.   -  person user1032677    schedule 24.04.2020
comment
Я не участвую в Math SE и не занимаюсь математикой, и у меня едва ли есть время поддерживать другие мои учетные записи SE (в основном активные здесь) ... Если вы хотите, вы можете ответить сами (и связать этот QA с комментарием, что ответ на этом основано или что-то в этом роде ...)   -  person Spektre    schedule 25.04.2020


Ответы (1)


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

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

обзор

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

M * A = C
M * B = D

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

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

  1. Масштаб самый простой

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

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

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

    so:

     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'))
    

    or

    translate(M*(C-A'))
    

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

Вот небольшой пример C ++ / VCL / старого 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 (будьте осторожны, они выражаются в градусах, а не в радианах).

Вот превью:

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

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

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

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

person Spektre    schedule 24.04.2020