Продвинутое рисование в табличном документе (стрелок и не только)

Программирование - Практика программирования

59
Вспоминаем геометрию и основы компьютерной графики. Матрицы и аффинные преобразования на плоскости.

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

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

Допустим, у нас заданы координаты точек звезды x', y'. И мы хотим отобразить звезду как на рисунке. Для этого нужно в пространстве табличного документа создать локальное пространство с заданным базисом (оси координат и длина единицы по каждой оси) и смещением начала координат.

По сути, наше локальное пространство это некая функция, которая будет превращать локальные координаты x', y' в глобальные координаты x, y.

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

 

Создадим базовые функции для операции с матрицами и векторами:

 
 СоздатьВектор(x,y)

Создаёт массив с тремя элементами [x,y,1]. Третья единица необходима для операции смещения, пока просто примите как данность.

 
 СоздатьМатрицу()

Создает двумерный массив 3x3 заполненный по диагонали единицами, в остальном нулями.

 
 УмножитьВекторНаМатрицу(Вектор, Матрица)

Возвращает вектор (массив вида [x,y,z]) - результат умножения вектора на матрицу.

 

Теперь нужно решить, как описать наше локальное пространство и как составить чудо-матрицу. Оказывается, это очень просто. Впишем векторы смещения (зеленый) и базиса (красный и синий) в матрицу как указано на рисунке:

Третий столбец не трогаем, он всегда [0, 0, 1].

Например, если взять единичную матрицу, то получим синий вектор вправо (1, 0). Красный вверх (0, 1) и смещение ноль (0, 0). Т.е. такая матрица задаст пространство идентичное глобальному и умножение вектора на такую матрицу вернет его самого.

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

 

Напишем процедуру создания матрицы локального пространства по этим векторам:

 
 СоздатьПространствоПоВекторам(Смещение, БазисВерх, БазисПраво)

Функция принимает три вектора и возвращает матрицу пространства.

Но задавать пространство векторами базиса не всегда удобно. Часто удобнее указать положение, ширину, высоту и угол поворота. Напишем функцию принимающую эти параметры:

 
 СоздатьПространствоПоУглу(Смещение, Ширина, Высота, УголГрадусов)

 

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

 
 Линия(ТабДок, Начало, Конец, Пространство)
 
 Ломанная(ТабДок, Точки, Замкнуть, Пространство)

 

И наконец, приступаем к написанию пользовательской, конечной функции. Вызывающий нашу функцию не должен знать все эти теории про векторы, матрицы и пространства. Он просто указывает, где и что нарисовать. Начнем с рисования прямоугольника:

 
 НарисоватьПрямоугольник(ТабДок, ЦентрX, ЦентрY, ДлинаX, ДлинаY, Поворот)

 

Теперь приступим к стрелке. Мы можем создать пространство указав саму стрелу как вектор "вверх" базиса. Тогда начало стрелы в локальных координатах будет находится в (0, 0), а конец в (0, 1). Но в таком случае при увеличении стрелы будет увеличиваться её наконечник. Поэтому поступим так: найдем точку основания наконечника и нарисуем отдельно наконечник, отдельно основную линию.

 
 НарисоватьСтрелку(ТабДок, НачалоX, НачалоY, КонецX, КонецY, ДлинаНаконечника, ШиринаНаконечника, Заострение)

В этой процедуре для удобства использованы дополнительные процедуры по работе с векторами - разница, вычисление длины и Lerp - линейно интерполирует значение между двумя векторами по указанному значению t. Все функции описаны ниже.

 

Можно рисовать сложные фигуры по точкам как в детских журналах. Для этого добавлена функция быстрого определения массива точек из строки. Например, птица:

 
  НарисоватьПтицу(ТабДок, ЦентрX, ЦентрY, Размер, Поворот)

 

Для рисования кривых и окружностей добавлены функции Безье и Окружность. Рисунок в табличном документе может быть типа эллипс и это гораздо быстрее и экономнее чем рисование отрезками, но корректно отобразить окружность в растянутых/повернутых пространствах можно только так.

Полный код формы примера. Необходимо на форму добавить табличный документ "ТабДок1" и привязать событие "ПриОткрытии".

 
 Полный текст формы обработки

Кому лень, может просто скачать обработку. Спасибо за внимание!

59

Скачать файлы

Наименование Файл Версия Размер
Рисунки.epf
.epf 9,77Kb
24.07.18
6
.epf 1.0 9,77Kb 6 Скачать

См. также

Комментарии
Сортировка: Древо
1. SlavaKron 24.07.18 16:56 Сейчас в теме
Неплохо. Следующий шаг нарисовать каркас 3д модели в центральной проекции.
2. A1ice1990 109 24.07.18 19:35 Сейчас в теме
Идея для следующей публикации:
gif файл это по сути пару маркеров в начале, а затем массив точек.
Собрать белое полотно - пару строчкек кода из цикла и двоичных данных.
Ну а дальше вы художник)
3. pm74 125 24.07.18 20:35 Сейчас в теме
Браво. Я всегда говорю, что здоровая конкуренция - двигатель прогресса. За безье отдельное спасибо
4. Dmitri_1C 99 24.07.18 23:57 Сейчас в теме
Выше всяких похвал.
Однозначно +.
5. Неопределено 25.07.18 04:54 Сейчас в теме
Теперь можно написать код, рисующий мультик. И почему я не сомневался, что эта публикация появится в ближайшее время?
6. it@contlog.ru 25.07.18 05:40 Сейчас в теме
Замечательно, если пойти далее можно так выводить файлы SVG
7. Antonov.AV 25.07.18 06:44 Сейчас в теме
+ Следующий шаг анимация
8. HAMMER_59 68 25.07.18 07:06 Сейчас в теме
Какие-то не типичные основы компьютерной графики. Насколько я помню из курса компьютерной графики, да и в документации по DirectX то же самое. Все начинается с рассмотрение поворота точки. Начальная точка А имеет координаты.
x = l * cos(a)
y = l * sin(а)
После поворота на угол бэта (b), получаем новые координаты
x = l * cos(a + b)
y = l * sin(a + b)

cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b)
sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b)

А вот после этого уже подгоняется под умножение матриц.
Далее из матриц берем A * B * C * D = A * (B * C * D)

Для перемещения и масштабирования матрицу увеличивают до 3 х 3
10. WalterMort 269 25.07.18 08:53 Сейчас в теме
(8) В документации по DirectX для вращения также используются матрицы, постоянно вычислять косинус и синус это дорогое удовольствие.

https://docs.microsoft.com/ru-ru/windows/uwp/gaming/working-with-2d-graphics-in-your-directx-game

"Вращение происходит, когда вы вращаете объект вокруг определенной оси или осей. При работе с векторным изображением вершины геометрических фигур умножаются по матрице вращения, чтобы получить повернутую вершину; при работе с растровым изображением можно использовать различные алгоритмы, каждый из которых дает большую или меньшую степень точности результатов. Как и при масштабировании и преобразовании, существуют программные интерфейсы, специально разработанные для операций вращения."
11. HAMMER_59 68 25.07.18 10:17 Сейчас в теме
(10) До какой буквы дочитали?
И что же в матрицах преобразования поворота подставляется, не косинус и синус угла поворота случаем?

Видимо до строки "А вот после этого уже подгоняется под умножение матриц" не осилили дочитать.

Выигрыш в использовании матриц, не в том, что тригонометрию использовать не надо, еще как надо, и вычислений для отдельно взятой точки нужно сделать больше, чем без матриц. Другое дело что одну и ту же матрицу преобразования можно использовать для множества точек, а также можно откатывать на любой этап преобразования, и именно в этом происходит экономия при расчетах.
9. Cерый 15 25.07.18 08:28 Сейчас в теме
Отлично!
Полагаю, следующий шаг - рисование в трехмерии, диаграммы в 1С до уровня Crystal Reports ...
12. HAMMER_59 68 25.07.18 11:18 Сейчас в теме
Как же сложно стало отыскать нормальную документацию по основам 3Д графики. Насчет DirectX, читать надо не галопом по европам, а DirectX SDK, когда еще был 7 DirectX теория по 3D графике занимала порядка тысячи страниц, сейчас думаю поболее будет.

Вот здесь неплохо описаны основы 3D графики, вдруг, кому-то действительно интересно.
Учебное пособие по компьютерной графике

А в статье, как рисование совы через геометрические фигуры:
Шаг1: Рисуем круг;
Шаг2: Рисуем овал;
Шаг3: Дорисовываем все остальное
13. WalterMort 269 25.07.18 15:52 Сейчас в теме
(12) Ну что же. Тогда вам стоит последовать моему примеру и написать свою публикацию на эту тему.
15. HAMMER_59 68 26.07.18 08:04 Сейчас в теме
(13) Вы, наверно, воспринимаете критику, как-будто мне полностью не нравится Ваша статья - это не так. Вполне нормальная статья, и думаю, многим она будет интересна.
Почему я не написал подобную статью:
1. Вся теория уже есть в интернете, и её очень и очень много, зачем переписывать то, что уже отлично изложено. Уверен, что у меня получится хуже.
2. 1С не предназначена для работы с графикой.

Почему я прицепился к теории - мне нравится математика, а Вы её как-то пропустили, причем подход то очень интересный. Понятно что в итоге приходят к свойству матриц: A * B * C * D = A * (B * C * D), но к этому нужно прийти.
Начинают с того самого первого вектора, и его координаты указывают от от вектора с 0 градусом
Ax = x * cos(a)
Ay = x * sin(a)

При повороте на угол b получаем новую точку B с координатами
Bx = x * cos(a + b)
By = x * sin(a + b)

раскладываем по тригонометрическим формулам
Bx = x * cos(a) * cos(b) - x * sin(a) * sin(b)
By = x * sin (a) * cos(b) + cos(a) * sin(b)

подставляем
Bx = Ax * cos(b) - Ay * sin(b)
By = Ay * cos(b) + Ax * sin(b)

выводят конечно через деление, напишу сразу результат
{Ax, Ay} * {} = Ax * cos(b) - Ay * sin(b)
-------------{}----Ay * cos(b) + Ax * sin(b)
т.е. не сложно подобрать нужную матрицу поворота
{cos(b), sin(b)}
{-sin(b), cos(b)}

Как сделать перемещение? А делают следующим образом, к вектору добавляют третье значение, теперь вектор (x, y, 1), и матрица преобразования уже не 2 х 2, а 3 x 3, для перемещения выглядит следующим образом
1 0 0
0 1 0
Tx Ty 1

Далее переходят к масштабированию. Записываем вектор (x, y, z) а координаты точки на экране (x/z, y/z). Матрица масштабирования выглядит
1 0 Wx
0 1 Wy
0 0 Wz

А вы как-то сразу перешли к относительным координатам.

3-х мерная графика практически ничем не отличается от двумерной, появляется ось Z, которая направлена вдаль, центр по x, y переносится в центр экрана. По нехитрой формуле вычисляется коэффициент для координат x, y при смещении по оси Z, т,е. при отдалении точки смещаются в центр.
17. WalterMort 269 26.07.18 09:57 Сейчас в теме
(15) добавьте к этому возможность неортогонального базиса и любимой математики станет еще больше. В статье тригонометрические функции используются исключительно для удобства в варианте создания пространства с заданным углом. Кстати не знаю, заметили или нет, данный вариант работает исключительно с ортогональным базисом в отличие от векторного, поэтому на полноценную замену не тянет. Упрощение.
18. WalterMort 269 26.07.18 10:47 Сейчас в теме
(15) Т.е. что я хочу акцентировать. Преобразования в пространстве в первую очередь векторные и матричные операции. А тригонометрия это плюшка сверху, добавленная потому что люди привыкли оперировать понятием "угол".
19. HAMMER_59 68 26.07.18 11:49 Сейчас в теме
(18) По-моему уже предельно ясно описал как тригонометрические преобразования привели к умножению матриц, но Вы все равно не видите тригонометрических преобразований. И ещё раз повторю, что это не моя точка зрения, это изложения материала по 3Д графики, ссылку я уже привел, тоже самое написано и в документации под Direct3D, и по OpenGL.
Насчет векторных преобразований. Не вижу ни одного векторного преобразования. Насколько я помню в 3Д графике используются произведения векторов, которое дает вектор перпендикулярно направленный к плоскости двух векторов, тем самым мы определяем плоскость расположена лицевой частью к нам, либо задней частью, в зависимости от этого для односторонних плоскостей мы их либо выводим на экран либо нет.

Тяжело вникнуть в Вашу особую теорию, когда вы пишете, там не нужны синусы и косинусы, вот ведь через вектор все можно сделать. Может Вы не знали что косинус - это отношение прилежащего катета к гипотенузе, а синус - отношение противолежащего катета к гипотенузе. Если вы и после этого считаете, что не используете синус и косинус, я тут Вам уже ничем помочь не могу.
20. WalterMort 269 26.07.18 13:06 Сейчас в теме
(19) Понимаю, что перебороть "как учили" сложно. Катеты, гипотенузы. Ортогональное пространство (где оси координат находятся под углом 90 градусов) это сильно частный случай. В рамках этого частного случая вектор можно описать как вращение (1,0) на угол "а". Если же ось Y направлена, например под углом 45 к оси X, то такого сделать будет нельзя.
Вы же математик, и должны понимать, что не все задачи сводятся в отображении прямоугольной картинки на прямоугольный монитор.
Мне было бы интересно посмотреть, как бы Вы переводили координаты из одного неортогонального пространство в другое с помощью катетов и гипотенузы и проч. тригонометрии, завязанной на угол 90 градусов.
21. HAMMER_59 68 26.07.18 14:26 Сейчас в теме
(20)
Понимаю, что перебороть "как учили" сложно. Катеты, гипотенузы. Ортогональное пространство (где оси координат находятся под углом 90 градусов) это сильно частный случай. В рамках этого частного случая вектор можно описать как вращение (1,0) на угол "а". Если же ось Y направлена, например под углом 45 к оси X, то такого сделать будет нельзя.


Т.е. на ваших рисунках оси координат не под 90 градусов расположены друг к другу о_О.
Ну давайте на примере, раз уж на то пошло.
Задаём фигуру - квадрат с равноудаленными точками от центра:
(-1, -1)
(-1, 1)
(1, 1)
(1, -1)

Приводим к виду
(-1, -1, 1)
(-1, 1, 1)
(1, 1, 1)
(1, -1, 1)

Теперь используем 2 матрицы, первая - матрица преобразования объекта M1, вторая матрица положения объекта на экране M2.
Вычислять результат будем следующим образом
M * M1 * M2
где M - матрица точек объекта.

Рассчитываем М2, для начала смещаем на 5 вправо, затем поворачиваем на 45 градусов.
(1, 0, 0)
(0, 1, 0) x
(0, 0, 1)

(1, 0, 0)
(0, 1, 0) x
(5, 0, 1)

(0.7, 0.7, 0)
(-0.7, 0.7, 0) =
(0 , 0 , 1)

(0.7, 0.7, 0)
(-0.7, 0.7, 0)
(3.5, 3.5, 1)

проверяем
(-1, -1, 1)
(-1, 1, 1) х
(1, 1, 1)
(1, -1, 1)

(0.7, 0.7, 0)
(-0.7, 0.7, 0) =
(3.5, 3.5, 1)

(3.5, 2.1, 1)
(2.1, 3.5, 1)
(3.5, 4.9, 1)
(4.9, 3.5, 1)

Повернуть не получится, сейчас проверим, как не получится
Для простоты возьмем матрицу поворота на 45 градусов, мы её уже рассчитали, т.е. M1 равно:
(0.7, 0.7, 0)
(-0.7, 0.7, 0)
(0 , 0 , 1)
M1 * M2 =
(0, 1, 0)
(-1, 0, 0)
(3.5, 3.5, 1)

Умножаем M на матрицу преобразований и о чудо
(4.5, 2.5, 1)
(2.5, 2.5, 1)
(2.5, 4.5, 1)
(4.5, 4.5, 1)

Всё получилось, вот ведь.
А если теперь добавить еще ось Z которая, перпендикулярна к осям X, Y уже получится 3-х мерное пространство.
22. WalterMort 269 26.07.18 16:16 Сейчас в теме
(21)
Т.е. на ваших рисунках оси координат не под 90 градусов расположены друг к другу о_О.


Я сделал похожее на 90 градусов, чтобы не усложнять статью, но ниже указал что оси могут быть не ортогональны. Посмотрите на прикрепленный к этому сообщению рисунок. Тут задано вполне себе нормальное пространство в виде параллелограма. Векторным образом матрица преобразования М строится как описано в статье:

(7, 6, 0) - 7,6 это вектор базиса "вправо"
(1, 5, 0) - 1,5 вектор базиса "вверх"
(5, 4, 1) - 5,4 смещение.

Точка в локальном пространстве 0,5 0.5 соответственно в глобальном:

(0.5, 0.5, 1) * М = (9, 9.5, 1)

Можно ли решить эту задачу зная не векторы базиса, а их углы относительно оси (1, 0) ? Можно. Сначала найдем через синус и косинус векторы базиса и потом опять же векторным способом получим матрицу ровно как я описал. А если мы изначально знаем векторы базиса, тригонометрия не нужна, вот о чем я толкую.

Всё получилось, вот ведь.

А я и не утвеждаю, что тригонометрия не работает, я говорю что к этой задаче она вторична.


Офф: Кстати векторный базис в вышеописанной матрице ((7,6),(1,5)) имеет ещё одно замечательное свойство. Его определитель |R| показывает во сколько раз изменится площадь фигуры при её переносе из локального пространства в глобальное.
Прикрепленные файлы:
23. HAMMER_59 68 27.07.18 06:38 Сейчас в теме
(22) Я на всякий случай проверил, какую изначально задачу Вы ставили:

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


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

Перемещение, поворот, масштабирование я описал в сообщение 15.
24. HAMMER_59 68 27.07.18 08:54 Сейчас в теме
(22) Как я уже написал, я знаю, крайне мало примеров практического использования проецирования на плоскость. Насколько помню, тени таким образом отображают, а также отражения одних предметов в других. Для меня это никогда особого интереса не вызывало.
Поэтому именно эту часть я изучал бегло, но именно этот вопрос мне попался на экзамене, было досадно. И именно поэтому не сразу разобрал, что же за новаторские решения Вы тут описываете.

Как Вы уже убедились я могу делать любые преобразования в 2-мерном пространстве, и систем координат могу использовать сколь угодно много, т.е. легко организую вращение колеса вокруг своей оси, а ось будет прикреплена к раме, и рама может при этом как угодно вращаться/перемещаться, при этом еще и камера (точка зрения) может как угодно меняться.

Для 3-мерного пространства, мало что изменится, кроме появления оси Z, и проецирования на область экрана, а матрицы преобразования будут подобными.

Ну может, Вы приведете примеры полезного использования приведенного вами подхода.

Я даже уже задумался сделать модель движения поршня в двигателе, ну чтобы точно развеять все сомнения насчет мощи новаторского подхода. Потом посмотрел как выводится линии в 1С. Потом оценил как это все реализовать без ООП. И сразу вспомнил выражение: "Пытаться натянуть сову на глобус".
По-моему проще внешнюю компоненту сделать, для работы, например, с OpenGL.
25. HAMMER_59 68 27.07.18 11:20 Сейчас в теме
(22) Насчёт того, как всё быстро и просто.

Как я уже писал, весь сыр бор с матрицами из-за одного свойства матриц A * B * C = A * (B * C). С помощью данного свойства легко осуществляется переход между системами координат, как правило: камера, мир, объект, но и объекты могут перемещаться относительно других объектов.

Для операции переноса + поворот (причем поворот то как раз не в отдельной системе координат, а системе координат изображения). В вашем новаторском способе.

1. Сначала вычисляете координаты проекции. Естественно при вычислении используются операции синуса / косинуса.
2. Затем получаете матрицу 3 х 3
3. Далее каждую точку умножаете на матрицу. 9 операций умножения + 6 сложения.

Тоже самое можно было бы сделать.
1. Получить значения синуса/косинуса, без всяких дополнительных операций.
2. Получили матрицу 2 x 2 просто подстановкой значений (в некоторых случаях нужно поменять знак).
3. Для каждой точки. Сначала выполняем операцию перемещения, это 2 операции сложения. Затем полученные координаты умножаем на матрицу 2 х 2. 4 операции умножения + 2 сложения.

Может вы и в других средах умеете писать, я даже готов ради такого случая освежить свой знания по Си шарп, и на нем написать пример. Могу и 3-х мерном пространстве и в 2-х. Чтобы голословным не быть, что в вашем подходе потенциала нет, а все решается совсем по-другому.
16. HAMMER_59 68 26.07.18 08:36 Сейчас в теме
(13) Ну и разу уж вы упомянули быстродействие: "Каждый раз вычислять синус и косинус слишком долго". Так в принципе, вся эта реализация, крайне медленная. Даже если опустить насколько "быстро" исполняется код 1С, программное вычисление - это слишком долго.
Графический процессор аппаратно в разы быстрее производит операции с матрицами, даже на порядок, ато и на пару порядков быстрее. Кроме того в видеокарте графический процессор не один а уже сотни, т.е. еще добавляется пару порядков к быстродействию.
Так что изначально вся эта затея не про скорость.
14. WalterMort 269 25.07.18 15:57 Сейчас в теме
Кстати о 3D, почему бы и нет. Нужно же до конца разобраться в кватернионах. Жалко, что не скоро найдется пара свободных вечеров.
Оставьте свое сообщение