Как сделать преобразование объекта

Обновлено: 05.07.2024

Есть несколько типов объектов, которые с помощью команды ПРЕОБРВТЕЛО можно преобразовать в выдавленные 3D тела. К этим объектам относятся замкнутые полилинии и круги, обладающие толщиной, а также сети и поверхности .

Несколько смежных объектов не получиться преобразовать в тело. Но тот же результат можно получить, если вначале объединить эти объекты. Например, выполняется расчленение 3D твердотельного ящика на области. Вначале с помощью ПРЕОБРВПВРХ следует преобразовать каждую область в поверхность. Затем с помощью команды ОБЪЕДИНЕНИЕ формируется сложный объект-поверхность. Наконец, с помощью команды ПРЕОБРВТЕЛО поверхность преобразуется в тело.

При преобразовании объектов-сетей в 3D тела форма нового твердотельного объекта приблизительно соответствует исходному объекту-сети, но не является его точной копией. В некоторой степени управлять этим различием можно путем указания, будет ли результат сглаженным или фасетчатым (SMOOTHMESHCONVERT). Кром е того, можно определить необходимость слияния (оптимизированного) получившихся в результате граней.

  • Сеть с зазорами между гранями. Редактирование с помощью гизмо может иногда приводить к появлению между гранями зазоров или отверстий. В некоторых случаях зазоры можно закрыть с помощью операции сглаживания объекта-сети.
  • Сеть, имеющая само пересекающиеся контуры. Если в результате внесенных изменений одна или несколько граней объекта-сети взаимно пересекаются, преобразование этого объекта в 3D тело невозможно.

В некоторых случаях сеть, которую невозможно преобразовать в твердотельный объект, можно преобразовать в поверхность.

Четвёртая статья будет разбита на две, первая часть говорит про построение перспективного искажения, вторая про то, как двигать камеру и что из этого следует. Задача на сегодня — научиться генерировать вот такие картинки:




Геометрия на плоскости

Линейные преобразования плоскости

Линейное на плоскости отображение задаётся соответствующей матрицей. Если мы возьмём точку (x,y), то её преобразование записывается следующим образом:

Самое простое (невырожденное) преобразование задаётся единичной матрицей, оно просто оставляет каждую точку на месте

Коэффициенты на диагонали матрицы задают растягивание/сжатие плоскости. Давайте проиллюстрируем картинкой: например, если мы запишем следующее преобразование:

То белый объект (квадрат с отрезанным углом) преобразуется в жёлтый. Красный и зелёный отрезки дают единичные векторы по оси x и y, соответственно.


Все картинки к этой статье сгенерированы вот этим кодом.

Зачем вообще использовать матрицы? Потому что это удобно. Начнём с того, что в матричной форме преобразование всего объекта можно записать вот таким образом:

Здесь преобразование то же, что и в предыдущем примере, а вот матрица в две строки и пять столбцов не что иное, как массив координат нашего куба с обрезанным углом. Мы просто взяли целиком массив, умножили на преобразование, и получили уже преобразованный объект. Красиво? Окей, согласен, притянуто за уши.

Настоящая причина в том, что крайне регулярно мы хотим, чтобы объект подвергся нескольким преобразованиями подряд. Представьте, что вы пишете в вашем коде функции преобразований типа

Этот код делает два линейных преобразования на каждую вершину объекта, а они исчисляются в миллионах. И преобразований зачастую мы хотим с добрый десяток. Дорого. А с матричным подходом мы перемножаем все матрицы преобразования и умножаем на наш объект один раз. В умножении мы можем ставить скобки где хотим, правда ведь?

Продолжаем разговор, мы знаем, что диагональные элементы нам дают масштабирование по осям. За что отвечают два других коэффициента матрицы? Давайте рассмотрим такое преобразование:



Не что иное, как простой сдвиг вдоль оси x. Второй анти-диагональный элемент даст сдвиг вдоль оси y. Таким образом, базовых линейных преобразований на плоскости только два: растягивание по осям и сдвиг вдоль оси. Постойте, скажут мне, а как же, например, вращение вокруг начала координат?

Выясняется, что вращение может быть представлено как композиция трёх сдвигов, здесь белый объект преобразован сначала в красный, затем в зелёный, затем в синий:


Но не будем ударяться в крайности, матрица вращения против часовой стрелки вокруг начала координат может быть записана напрямую (помните про расстановку скобок?):

Перемножать мы можем, конечно, в любом порядке, только давайте не забывать, что для матриц умножение некоммутативно:

Что нормально, сдвинуть и затем повернуть (красный объект) не то же самое, что сначала повернуть, а затем сдвинуть (зелёный объект):


Аффинные преобразования на плоскости

То есть, любое линейное преобразование на плоскости это композиция растягиваний и сдвигов. Что означает, что какой бы ни была матрица нашего преобразования, начало координат всегда перейдёт в начало координат. Таким образом, линейные преобразования — это прекрасно, но если мы не можем представить элементарного параллельного переноса, то наша жизнь будет печальна. Или можем? А что, если добавить его отдельно и записать аффинное преобразование как композицию линейной части и параллельного переноса? Примерно вот так:

Это, конечно, прекрасная запись, но вот только давайте посмотрим, на что похожей выглядит композиция двух таких преобразований (я напоминаю, что в реальной жизни нам нужно уметь аккумулировать десятки преобразований):

Это начинает выглядеть крайне неприятно уже для одной-единственной композиции. Попробуйте преобразовать это выражение, чтобы применить к нашему объекту только одно преобразование вида линейная часть + параллельный перенос. Лично мне очень не хочется этого делать.

Однородные координаты

А что же делать? Колдовать! Представьте теперь, что я допишу руками одну строчку и один столбец к нашей матрице преобразования и добавлю третью координату, которая равна единице у вектора, который мы преобразовываем:

При умножении этой 3x3 матрицы и нашего вектора, дополненного единицей, мы снова получили вектор с единицей в третьей компоненте, а остальные две имеют ровно тот вид, который мы хотели! Колдунство.

На самом деле, идея очень простая: параллельный перенос не является линейной операцией в двумерном пространстве.
Поэтому мы погружаем наше двумерное пространство в трёхмерное (добавив единицу в третью компоненту). Это означает, что наше двумерное пространство это плоскость z=1 внутри трёхмерного. Затем мы делаем линейное преобразование в трёхмерном пространстве и проецируем всё трехмерное пространство обратно на нашу физическую плоскость. Параллельный перенос от этого не стал линеен, но пайплайн всё же прост.

Как именно мы проецируем трёхмерное пространство обратно в нашу плоскость? Очень просто:

Секундочку, но ведь на ноль делить нельзя!

  • Мы погружаем наше 2d пространство в 3d, сделав его плоскостью z=1
  • Делаем что хотим в 3d
  • Для каждой точки, которую хотим спроецировать обратно в 2d, проводим прямую между началом координат и данной точкой и ищем её пересечение с физической плоскостью z=1.


Теперь давайте представим вертикальный рельс, проходящий через точку (x,y,1). Куда спроецируется точка (x,y,1)? Конечно же, в (x,y):


Теперь давайте начнём скользить вниз по рельсу, например, точка (x, y, 1/2) спроецируется в (2x, 2y):


Продолжим скользить: точка (x,y,1/4) спроецируется в (4x, 4y):


Продолжая скользить к нулю по z, наша проекция уходит всё дальше и дальше от центра координат по направлению (x,y).
То есть, точка (x,y,0) проецируется в бесконечно далёкую точку в направлении (x,y). А что это? Правильно, это вектор!
Однородные координаты дают возможность различать вектор и точку. Если программист пишет vec2 v(x,y), это вектор или точка?
Трудно сказать. А в однородных координатах всё, что с нулём по третьей компоненте, это вектор, всё остальное конечные точки.
Смотрите: вектор + вектор = вектор. Вектор-вектор = вектор. Точка + вектор = точка. Ну не здорово ли?

Пример составного преобразования

Я уже говорил, что нам нужно уметь накапливать десятки преобразований. Почему? Предположим, вам нужно повернуть плоский объект вокруг точки (x0, y0). Как это сделать? Можно пойти и искать формулы, а можно сделать самим, ведь у нас есть все инструменты.
Мы умеем вращать вокруг центра координат, мы умеем сдвигать. Что ещё надо? Сдвигаем x0,y0 в центр координат, вращаем, возвращаем назад. Халява!

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

Постойте, а имею ли я право трогать нижнюю строку матрицы 3x3?

Ещё как! Давайте применим вот это преобразование:

к нашему стандартному тестовому объекту. Напоминаю, что тестовый объект белый, единичные икс и игрек вектора показаны красным и зелёным, соответственно


Вот наш преобразованный объект:


И вот тут начинается самое интересное. Помните наше упражнение про игрек-буфер? Здесь мы будем делать практически то же самое.
Мы будем проецировать наш двумерный объект на прямую x=0. Причём теперь усложним задачу: проекция будет центральной, наша камера находится в точке (5, 0) и смотрит в начало координат. Чтобы найти проекцию, мы должны провести прямые, проходящие через точку камеры и каждую вершину нашего объекта (жёлтые прямые), а затем найти их пересечение с прямой экрана (белая вертикальная).


А теперь давайте уберём оригинальный объект и вместо него нарисуем трансформированный.




Если мы используем обычную ортогональную проекцию нашего трансформированного объекта, то мы найдём ровно те же самые точки!
Ведь что делает это отображение? Оно каждое вертикальное ребро оставляет вертикальным, но при этом растягивает те, которые близко к камере, и сжимает те, что дальше от камеры. Правильно подобрав коэффициент растяжения-сжатия мы можем как раз достичь эффекта, что простой ортогональной проекцией мы получаем изображение в перспективном искажении! В следующем параграфе мы добавим одно измерение и покажем, откуда взялся коэффициент -1/5.

Пора перейти к трём измерениям

Давайте объяснять только что произошедшую магию.
Как и в случае двумерных аффинных преобразований, в трёхмерном пространстве мы тоже будем использовать однородные координаты.
Берём точку (x,y,z), погружаем её в четырёхмерное пространство, добавив единицу в четвёртую компоненту, преобразуем в четырёх измерениях и проецируем обратно в 3d. Например, возьмём такое преобразование:

Проекция на 3д даёт следующие координаты:

Хорошо запомним этот результат, но на пару минут его отложим. Давайте вернёмся к стандартному определению центральной проекции в обычном 3д, без однородных координат и прочих экзотических вещей. Пусть у нас будет точка P=(x,y,z), которую мы хотим спроецировать на плоскость z=0, камера находится на оси z на расстоянии c от центра координат.


Мы знаем, что треугольники ABC и ODC подобны. То есть, мы можем записать |AB|/|AC|=|OD|/|OC| => x/(c-z) = x'/c.

Рассматривая треугольники CPB и CP'D, можно легко прийти к подобной записи и для координаты y:

Итак, это очень-очень похоже на результат проекции через однородные координаты, только там это всё считалось одним матричным умножением. Мы вывели зависимость коэффициентов r = -1/c.

Хотя если вы просто возьмёте эту формулу, не поняв весь предыдущий текст, то я вас ненавижу. Итак, если мы хотим построить центральную перспективу с (важно!) камерой, находящейся на оси z на расстоянии c от начала координат, то сначала мы погружаем трёхмерные точки в четырёхмерное пространство, добавив 1. Затем умножаем на следующую матрицу и проецируем результат обратно в 3D:

Мы деформировали наш объект так, что теперь для построения проволочного рендера с перспективой нам достаточно просто забыть про новополученную координату z. Если мы хотим строить z-буфер, то, разумеется, мы её используем. Слепок кода доступен на гитхабе. Результат его работы виден в самом начале нашей статьи.


Модуль 3D Transforms расширяет спецификацию CSS 2D Transforms, позволяя преобразовывать элементы в трехмерном пространстве. Новые функции преобразования для свойства transform выполняют трехмерные преобразования, расширяя координатное пространство до трех измерений, добавляя ось Z, перпендикулярную плоскости экрана, которая увеличивается по направлению к зрителю, а дополнительные свойства позволяют контролировать взаимодействие вложенных трехмерных преобразованных элементов.

Хотя некоторые значения свойства transform позволяют преобразовывать элемент в трехмерной системе координат, сами элементы не являются трехмерными объектами. Они существуют в двумерной плоскости (плоская поверхность) и не имеют глубины.

CSS3 3D-трансформации элементов

Поддержка браузерами

IE: 10.0
Firefox: 16.0, 10. -moz-
Chrome: 36.0, 12.0 -webkit-
Safari: 4.0 -webkit-
Opera: 23.0, 15.0 -webkit-
iOS Safari: 9, 7.1 -webkit-
Opera Mini: —
Android Browser: 44, 4.1 -webkit-
Chrome for Android: 44

1. Свойство transform-style

По умолчанию преобразованные элементы создают плоское представление своего содержимого. Свойство transform-style позволяет преобразованным 3D-элементам и их 3D-потомкам использовать общее трехмерное пространство, выстраивая иерархии трехмерных объектов. Отображение 3D-потомков определяется моделью — так называемым контекстом 3D-рендеринга. Отображение зависит от z-позиции элементов в трехмерном пространстве, и если 3D-преобразования этих элементов вызывают пересечение, то они отображаются с пересечением.

Свойство устанавливается для родительского элемента.

Свойство не наследуется.

transform-style
Значения:
flat Значение по умолчанию. Все дочерние элементы отображаются плоскими в двумерной плоскости блока-контейнера.
preserve-3d Располагает элементы в трехмерном пространстве.
initial Устанавливает значение свойства в значение по умолчанию.
inherit Наследует значение свойства от родительского элемента.

Некоторые значения CSS-свойств элемента, для которого задано transform-style: preserve-3d , изменяют используемое значение на flat и предотвращают создание или расширение контекста 3D-рендеринга:

  • overflow : любое значение, кроме visible или clip .
  • opacity : любое значение меньше 1 .
  • filter : любое значение, кроме none .
  • clip : любое значение, кроме auto .
  • clip-path : любое значение, кроме none .
  • isolation : если задано значение isolate .
  • mask-image : любое значение, кроме none .
  • mask-border-source : любое значение, кроме none .
  • mix-blend-mode : любое значение, кроме normal .

2. Свойство perspective

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

Свойства perspective и perspective-origin можно использовать для добавления ощущения глубины в сцену, делая элементы выше по оси Z (ближе к зрителю) и кажущимися большими, а те, которые находятся дальше — меньшими. Масштаб пропорционален d / (d - Z) , где d — значение перспективы, является расстоянием от плоскости рисования до предполагаемого положения глаза зрителя.


Рис. 1. Зависимость 3D-перспективы и положения элемента относительно оси Z

Если 3D-перспектива задается с помощью функции perspective() , 3D-пространство активизируется только для одного элемента. Свойство perspective активирует 3D-пространство внутри элемента, содержащего дочерние трансформированные элементы и применяется к ним.

Свойство не наследуется.

perspective
Значения:
длина Задает расстояние до центра проекции, т.е. расстояние по оси Z. Значение может быть любым положительным числом, заданным в единицах длины. Чем больше значение, тем менее выражен эффект. 0 означает отсутствие перспективы.
none Значение по умолчанию. Означает отсутствие перспективы.
initial Устанавливает значение свойства в значение по умолчанию.
inherit Наследует значение свойства от родительского элемента.

perspective-3d

Рис. 2. Примеры разных значений 3D-перспективы

3. Свойство perspective-origin

Обычно предполагаемое положение глаза зрителя находится в центре рисунка. Свойство perspective-origin управляет точкой начала координат, позволяя изменять направление трансформации дочернего 3D-элемента. Свойство должно использоваться вместе со свойством perspective для родительского элемента и свойством transform для дочернего элемента.

Свойство не наследуется.

perspective-origin
Значения:
позиция точки Свойство принимает два значения, первое задает координату X, второе — Y. Свойство может принимать следующие значения:
% — для горизонтального смещения перспективы определяется относительно ширины виртуальной рамки, для вертикальное смещения — относительно высоты;
значение, указанное в единицах длины задает фиксированную длину смещения. Значение смещения по горизонтали и вертикали представляет собой смещение от верхнего левого угла виртуальной рамки;
top вычисляется в 0% для вертикального положения, если задано одно или два значения, в противном случае определяет верхний край как исходную точку для следующего смещения;
right вычисляется в 100% для горизонтального положения, если задано одно или два значения, в противном случае указывает правый край в качестве исходной точки для следующего смещения;
bottom вычисляется в 100% для вертикального положения, если задано одно или два значения, в противном случае указывает нижний край в качестве исходной точки для следующего смещения;
left вычисляется в 0% для горизонтального положения, если задано одно или два значения, в противном случае определяет левый край в качестве исходной точки для следующего смещения;
center вычисляется 50% ( left 50% ) для горизонтального положения, если горизонтальное положение не указано иначе, или 50% ( top 50% ) для вертикального положения, если оно есть.
Значение по умолчанию 50% 50% .
initial Устанавливает значение свойства в значение по умолчанию.
inherit Наследует значение свойства от родительского элемента.

perspective-origin

Рис. 3. Примеры задания точки трансформации

4. Свойство backface-visibility

Используя трехмерные преобразования, можно преобразовать элемент так, чтобы его обратная сторона была видна. 3D-преобразованные элементы отображают одинаковое содержимое с обеих сторон, поэтому обратная сторона выглядит как зеркальное отображение лицевой стороны. Свойство backface-visibility позволяет делать элемент невидимым, когда его обратная сторона обращена к зрителю.

Свойство полезно, когда вы создаете флип-карту, размещая два элемента вплотную друг к другу, или куб из 6 элементов.

Свойство не наследуется.

backface-visibility
Значения:
visible Значение по умолчанию. Указывает, что обратная сторона видна.
hidden Скрывает обратную сторону элемента.
initial Устанавливает значение свойства в значение по умолчанию.
inherit Наследует значение свойства от родительского элемента.

5. Функции 3D-трансформации

Свойство задает вид как 2D, так и 3D-преобразований элемента. 3D-преобразования описываются с помощью функций трансформации, перечисленных в таблице ниже.

Чтобы разложить объект на массив нужно обратиться к конструктору Object и вызвать у него метод entries() , где в качестве параметра передать интересующий нас объект.

Пусть у нас будет такой объект для трансформации:

Вывод в консоль браузера:

Объект для разложения на массив - JavaScript

Объект для разложения на массив - JavaScript

Применим метод entries() :


Вывод в консоль браузера:

Массив из объекта - JavaScript

Массив из объекта - JavaScript

Теперь ключи объекта стали первыми элементами массивов, а значения - вторыми элементами. Каждая пара "ключ/значение" имеет свой индекс в массиве. Мы разложили объект на массив.

Читайте также: