Вращение треугольника вокруг себя по оси c с использованием трех вершин

Мне нужно повернуть треугольник (корабль) вокруг самого себя.
Вот что у меня получилось, но это не работает. Он становится все меньше и меньше, пока не исчезнет.

void RotatePoint(Point *P, float angle)
{
    float theta = angle * (180/3.1415);
    P->x = (P->x * cos(theta)) - (P->y * sin(theta));
    P->y = (P->y * cos(theta)) + (P->x * sin(theta));
}

void RotateShip(Ship *ship)
{
    Rotate(&ship->A, rotateAngle);
    Rotate(&ship->B, rotateAngle);
    Rotate(&ship->C, rotateAngle);
}

Точка P — это точка, которую я хочу повернуть, а точка C — это центр треугольника. Я думал, что если я поверну все три вершины, треугольник повернется.

В моем случае я инициализирую так:

void initShip(Ship *ship)
{
    ship->center.x = (SCREEN_W)/2.0;
    ship->center.y = (SCREEN_H)/2.0;
    ship->A.x = 0;
    ship->A.y = -5;
    ship->B.x = 15;
    ship->B.y = 25;
    ship->C.x = -15;
    ship->C.y = 25;
    ship->color = al_map_rgb(255, 255, 255);
}

Корабль A, B и C — это расстояние от центра треугольника. Я рисую его, добавляя A, B и C к центральной вершине.

A=-0.699857,-19.963261
A=-0.000857,-19.951065
A=-0.699001,-19.914387
A=-0.001712,-19.902250
A=-0.698147,-19.865631
A=-0.002565,-19.853554

Я нажимаю одну клавишу назад и одну вперед, заставляя ее вращаться по часовой стрелке и против часовой стрелки. обратите внимание, как A сжимается. Я не знаю, что я делаю. Я должен вернуться к 20.00, когда он достигнет вершины. Таким образом, мой треугольник сужается. Я использую cos(0,035) и sin(0,035), что означает 2 градуса.


person Lucas Augusto    schedule 25.06.2017    source источник
comment
Почему это не работает? Что вы сделали для его отладки?   -  person nicomp    schedule 25.06.2017
comment
Он просто исчезает. функция рисования такова: void drawShip(Ship *ship) { al_draw_triangle(ship->center.x + ship->A.x, ship->center.y + ship->A.y, ship->center.x + ship->B.x, ship->center.y + ship->B.y, ship->center.x + ship->C.x, ship->center.y + ship->C.y, ship->color, 1); } Когда я нажимаю клавишу, чтобы повернуть его, он исчезает. Что-то должно быть не так с моей математикой, но я не могу понять, что.   -  person Lucas Augusto    schedule 25.06.2017
comment
Пожалуйста, сделайте минимально воспроизводимый пример, не нужно рисовать весь рисунок и другие графические элементы, просто main() который печатает координаты несколько раз; демонстрируя, как они становятся меньше, особенно как быстро.   -  person Yunnosch    schedule 25.06.2017
comment
Среди прочего я хочу увидеть типы используемых вами координат. Внутр.? Плавать? Двойной?   -  person Yunnosch    schedule 25.06.2017
comment
Координаты становятся меньше относительно центра? Или корабль тоже движется в сторону (0,0)?   -  person Yunnosch    schedule 25.06.2017
comment
Корабль только сжимается или еще и вращается?   -  person Yunnosch    schedule 25.06.2017
comment
В чем разница между Rotate(...) как вызвано и RotatePoint(...) как определено? Что такое rotateAngle, используемое внутри RotateShip(...)?   -  person Yunnosch    schedule 25.06.2017
comment
Ваше значение для pi является неточным. Используйте M_PI, определенный в <math.h>   -  person chqrlie    schedule 25.06.2017
comment
Координаты, которые вы показываете, похоже, подтверждают проблему медленного сокращения, вызванную сохранением координат вместо угла. Улучшает ли предложенное мной решение?   -  person Yunnosch    schedule 25.06.2017
comment
Да! Проблема решена. большое спасибо!   -  person Lucas Augusto    schedule 25.06.2017


Ответы (2)


В OP есть классическая ошибка: использование временного (или промежуточного) значения, вместо которого следует использовать исходное/начальное значение.

В качестве упрощенного примера рассмотрим случай, когда у вас есть три переменные, a, b и c, и вы хотите повернуть их значения на одну переменную влево:

    a = b;
    b = c;
    c = a;  /* Oops! Won't work! */    

Последнее присваивание является проблемой, потому что a больше не является исходным значением! Вы не можете упорядочить задания таким образом, чтобы избежать этой проблемы; единственное, что меняется, это то, какая переменная пострадает от проблемы. Чтобы решить эту проблему, вам нужно использовать новую временную переменную для хранения исходного значения:

    t = a;
    a = b;
    b = c;
    c = t;

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

Решение состоит в том, чтобы описать форму корабля в отдельных переменных или использовать константы в функции обновления корабля.)

Допустим, у нас есть переменная dir, которая указывает направление в радианах, повернутое против часовой стрелки сверху, 0 вверх (к отрицательной оси y), π/2 (и -3π/2) влево (к отрицательной оси x), π (и -π) вниз, 3π/2 (и -π/2) вправо и так далее. Если deg в градусах, dir = deg * 3.14159265358979323846 / 180.0. Мы также можем использовать функцию atan2(), чтобы узнать dir: dir = atan2(-x, y).

Когда dir = 0, ОП хочет A = { 0, -5 }, B = { 15, 25 } и C = { -15, 25 }. Если мы определим Adir = 3.14159, Ar = 5, Bdir = -0.54042, Br = sqrt(15*15+25*25) = 29.15476, Cdir = 0.54042 и Cr = 29.15476, то вершины корабля

A.x = center.x + Ar*sin(dir + Adir);
A.y = center.y + Ar*cos(dir + Adir);
B.x = center.x + Br*sin(dir + Bdir);
B.y = center.y + Br*cos(dir + Bdir);
C.x = center.x + Cr*sin(dir + Cdir);
C.y = center.y + Cr*cos(dir + Cdir);

Если ОП хочет исправить форму корабля в функции rotateShip, то

void rotateShip(Ship *s, double rotateAngle)
{
    s->A.x = s->center.x +  5.00000 * sin(rotateAngle + 3.14159);
    s->A.y = s->center.y +  5.00000 * cos(rotateAngle + 3.14159);
    s->B.x = s->center.x + 29.15476 * sin(rotateAngle - 0.54042);
    s->B.y = s->center.y + 29.15476 * cos(rotateAngle - 0.54042);
    s->C.x = s->center.x + 29.15476 * sin(rotateAngle + 0.54042);
    s->C.y = s->center.y + 29.15476 * cos(rotateAngle + 0.54042);
}

Лично я бы определил форму корабля, используя переменное количество вершин:

typedef struct {
    double  x;
    double  y;
} vec2d;

typedef struct {
    vec2d        center;
    size_t       vertices;
    const vec2d *shape;     /* Un-rotated ship vertices */
    double       direction; /* Ship direction, in radians */
    vec2d       *vertex;    /* Rotated ship vertices */
} Ship;

const vec2d default_shape[] = {
    {   0.0, -5.0 },
    { -15.0, 25.0 },
    {  15.0, 25.0 },
};

void updateShip(Ship *ship)
{
    const double c = cos(ship->direction);
    const double s = sin(ship->direction);
    size_t       i;

    for (i = 0; i < ship->vertices; i++) {
        ship->vertex[i].x = ship->center.x + c*ship->shape[i].x - s*ship->shape[i].y;
        ship->vertex[i].y = ship->center.y + s*ship->shape[i].x + c*ship->shape[i].y;
    }
}

void initShip(Ship *ship, const size_t vertices, const vec2d *shape)
{
    ship->center.x = 0.5 * SCREEN_W;
    ship->center.y = 0.5 * SCREEN_H;

    if (vertices > 2 && shape != NULL) {
        ship->vertices = vertices;
        ship->shape    = shape;
    } else {
        ship->vertices = (sizeof default_shape) / (sizeof default_shape[0]);
        ship->shape    = default_shape;
    }

    ship->direction = 0;

    ship->vertex = malloc(ship->vertices * sizeof ship->vertex[0]);
    if (!ship->vertex) {
        fprintf(stderr, "Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    updateShip(ship);
}

В updateShip мы используем 2D-вращение на ship->direction, чтобы повернуть модель корабля, заданную вершинами в shape[], сохраняя повернутые и перемещенные координаты в vertex[].

    x_current = x_center + x_original * cos(direction) - y_original * sin(direction);
    y_current = y_center + x_original * sin(direction) + y_original * cos(direction);

как определено, например, в статья в Википедии о вращении. Обратите внимание, что исходные координаты x_original и y_original (или значения в массиве shape[] в структуре корабля) никогда не изменяются.

Таким образом, вы можете позволить игроку «обновить» свой корабль, просто изменив shape, чтобы он указывал на новую форму корабля, и vertices, чтобы отразить это число.

person Nominal Animal    schedule 25.06.2017
comment
Вы хотите default_shape --› default_ship? - person chux - Reinstate Monica; 27.06.2017
comment
@chux: Хороший вопрос, спасибо! На самом деле, потому что я хочу, чтобы ОП (и другие, читающие этот ответ) понимали разницу между текущими вершинами корабля (в его текущем переводе и ориентации) и формой корабля (исходной, не повернутой и не переведенной), я решил использовать default_shape. Я не знаю, работает ли это (помогает различать оригинал/неповернутый и текущий/повернутый и переведенный), но пока это лучшее, что я могу сделать. - person Nominal Animal; 28.06.2017

Я могу воспроизвести быстрое сжатие (при одновременном вращении) с координатами в int.
(это было бы намного проще на основе MCVE....).
С координатами в float сжимается намного медленнее, но все равно сжимается.

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

Чтобы полностью избежать сжатия:
не изменяйте относительные координаты для поворота. Вместо этого храните относительные координаты как константы вместе с ориентацией корабля как угол в двойном значении.

Затем вращайте, увеличивая/уменьшая угол (огибаясь, чтобы оставаться в пределах -Pi ... +Pi).
Затем рисуйте, всегда применяя изменяющийся угол к постоянным относительным координатам.

(Я могу показать вам подробно, только если вы предоставите MCVE.)

Таким образом, накопленные ошибки приведут лишь к небольшой и медленно нарастающей разориентировке,
которая, скорее всего, не будет замечена пилотом - и затем будет исправлена ​​пилотом.
"Хм, корабль еще не завершил 360, которые я хотел. Ну ладно, я повернусь еще немного ».

Кстати, я не доверяю тому, как вы используете углы в качестве параметров для cos() и sin().
Или, говоря иначе, я думаю
theta = angle * (180/3.1415); -> theta = angle; для разворота через Pi .
theta = angle * (180/3.1415); -> theta = angle * (3.1415/180); для разворота на 180°.
Для вашей реализации вы получаете разворот на угол (Pi*3.1415/180), для которого я не вижу причины.

Я также рекомендую использовать соответствующие константы из math.h (например, M_PI) вместо вашей собственной константы с 4 знаками после запятой.

person Yunnosch    schedule 25.06.2017