Не так давно я увидел статью, в которой автор рассказывает, как он заполнял коробки товарами. Для меня в ней круто всё, но больше всего привлекла визуализация результата. Мне стало интересно, насколько 1С умеет работать с 3D графикой и это вылилось в ряд статей, в которых я показывал, как постепенно учился работать с двухмерной графикой. Теперь я хочу продемонстрировать, как дошёл до трёхмерной системы координат. Штатными средствами.
На самом деле, вычисление вершин трёхмерных объектов является довольно несложной проблемой. Это всё та же задача со стрелками часов из моего предыдущего поста, только стрелок становится больше. Достаточно знать длины сторон и угол. Я понял это не сразу, и примитивные фигуры рисовал, жёстко прописывая координаты.
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = -50;
НоваяКоордината.У = 50;
НоваяКоордината.З = 0;
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = -50;
НоваяКоордината.У = -50;
НоваяКоордината.З = 0;
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = 50;
НоваяКоордината.У = 50;
НоваяКоордината.З = 0;
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = 50;
НоваяКоордината.У = -50;
НоваяКоордината.З = 0;
Это квадрат в трёхмерном пространстве.
Когда я дошёл до рисования куба, терпеть это надоело и его координаты я рассчитал… преобразуя числа в двоичную систему счисления. Это глупо, но это было первым, что пришло в голову на тот момент и это работает, а также помогает сократить код за счёт отказа от прописывания координат вручную. У нас ведь три оси, а вершин у куба восемь. 24 строки тупого кода против этого:
Для Счетчик = 0 По 7 Цикл
Результат = "";
Число = Счетчик;
Пока Число > 0 Цикл
Результат = Строка(Число % 2) + Результат;
Число = Цел(Число / 2);
КонецЦикла;
Пока СтрДлина(Результат) < 3 Цикл
Результат = "0" + Результат;
КонецЦикла;
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = ?(Лев(Результат, 1) = "0", -40, 40);
НоваяКоордината.У = ?(Прав(Лев(Результат, 2), 1) = "0", -40, 40);
НоваяКоордината.З = ?(Прав(Результат, 1) = "0", -40, 40);
КонецЦикла;
Если покрутиться вокруг этих координат, получится вот так:
Некоторые коллеги не сразу поняли что это, поэтому на всякий случай поясню: каждая вершина соединена с каждой вершиной прямой линией. Почему? Потому что могу, да и было интересно как это будет выглядеть.
Далее я решил нарисовать шар и тут как раз пригодилась задача с часами. Нам нужно нарисовать часы и каждый час заполнить перпендикулярными им часами. Не знаю, как объяснить точнее. Вот код, а под ним результат.
А = 0;
Пока А <= 3 Цикл //На анимации это значение больше
Б = 0;
Пока Б <= 7 Цикл //На анимации это значение больше
НоваяКоордината = ТаблицаКоординат.Добавить();
НоваяКоордината.Х = (70 * Sin(А) * Sin(Б));
НоваяКоордината.У = (70 * Sin(А) * Cos(Б));
НоваяКоордината.З = (70 * Cos(А));
Б = Б + 0.2;
КонецЦикла;
А = А + 0.1;
КонецЦикла;
70 это длина стороны треугольника, или радиус шара.
Тут я его разукрасил и покрутился вокруг него:
На моём железе такой скорости отрисовки с такой детализацией добиться нельзя, поэтому гифка редактировалась вручную, отсюда и вотермарка.
Самое интересное заключается в том, что нашу фигуру необходимо вывести на дисплей, а он ни разу не трёхмерный. Всё что у нас есть — это табличный документ и он не в курсе, что такое ось z. Поэтому координаты необходимо перевести из трёхмерного пространства в двухмерное. По специальности я не настоящий сварщик, а радиоинженер и в университете из программирования изучал только делфи на уровне конкатенации строк и ассемблер, который в данной задаче вообще не помощник. Я примерно помню, что в геометрии можно всё вертеть с помощью матриц, но изучал их около месяца, да и то на первом семестре. В общем, сделал как смог:
Рад = 3.1415926535897932/180;
Для Каждого Точка Из ТаблицаКоординат Цикл
Х = -1 * Точка.Х * Sin(Горизонт * Рад) + Точка.З * Cos(Горизонт * Рад);
У = -1 * (Точка.Х * Cos(Горизонт * Рад) + Точка.З * Sin(Горизонт * Рад)) * Sin(Вертикаль * Рад) + Точка.У * Cos(Вертикаль * Рад);
НоваяТочка = КоординатыЭкрана.Добавить();
НоваяТочка.Х = Х + 75;
НоваяТочка.У = У + 75;
КонецЦикла;
Если не умножать результат на -1, мы будем не вращать объект, а сжимать его. Смещение координат сделано по той причине, что фигуры рисовались в начале трёхмерного пространства, т.е. точка 0;0;0 находится внутри фигуры. Без этого смещения в табличный документ попадёт только четверть фигуры.
У нас есть два ползунка, которые отвечаю за углы, с которых мы смотрим на наш объект. Мне показалось, это проще чем крутить сам объект — так его координаты остаются неизменными. Перемещаемся только мы, относительно него. Получается, наш объект находится внутри сферы, по границе которой мы двигаемся. Всё вернулось к задаче со стрелками часов в более сложном исполнении. Теперь мы получаем на выходе проекции точек из трёхмерного на двухмерное пространство, которые можно вывести в табличный документ, или не в табличный документ. Единственным минусом данного метода является топорная проекция, не имеющая перспективы, что особенно хорошо заметно на пирамиде. За счёт того, что все окружающие объекты мы привыкли видеть в перспективе, при вращении кажется, что тыльная сторона основания шире фронтальной. Поэтому если кто-то подскажет как сделать преобразование координат с учётом перспективы, я буду очень благодарен.
Все моменты я описал, но на всякий случай выложил обработку, которая умеет генерировать геометрические фигуры в трёхмерном пространстве и выводить на экран. Она тестировалась на платформе 8.3.10. В ней присутствует удобное управление углами наклона, привязанное к клавишам WASD. Также есть кнопка "Демо", запускающая автоматическое вращение текущего объекта. Вращение шара будет происходить медленнее, это сделано специально чтобы исключить зависание.