Направление и расстояние от одного объекта до другого.
Если вычесть координаты одной точки в пространстве из координат другой, то получим вектор, который “выходит” из второй точки и “заканчивается” в первой:
// Gets a vector that points from the player's position to the target's. var heading = target.position - player.position;
Помимо того, что этот вектор указывает в направлении объекта, модуль данного вектора равен расстоянию между двумя позициями. Часто нам требуется нормированный вектор, который показывает направление к цели, а также расстояние (скажем, для управления снарядом). Расстояние между двумя объектами равно модулю направляющего вектора, он может быть нормирован, достаточно разделить на его модуль:-
var distance = heading.magnitude; var direction = heading / distance; // This is now the normalized direction.
Лучше выбрать именно такой подход, нежели использовать модуль и нормаль отдельно, из-за их склонности нагружать CPU (оба используют вычисления квадратных корней).
Если расстояние нужно лишь для сравнения (скажем, проверка на достаточную удаленность), тогда вообще можно не вычислять значение модуля. Свойство sqrMagnitude возвращает квадрат модуля, оно высчитывается подобно расстоянию, но без затратной по времени операции нахождения квадратного корня. Вместо того, что сравнивать модуль с известным расстоянием, можно сравнить квадрат модуля с квадратом расстояния:-
if (heading.sqrMagnitude < maxRange * maxRange) < // Target is within range. >
Это намного более эффективно, чем вычислять именно модуль при сравнении.
Иногда, требуется узнать направление к надземной цели. Например, представьте, что игроку, который стоит на земле нужно приблизиться к парящему в воздухе предмету. Если вычесть координаты позиции игрока из координат цели, тогда полученный вектор будет указывать вверх и по направлению к цели. Этот вариант не подходит, чтобы придать ориентацию компоненту transform игрока, так как он тоже будет указывать вверх. Что тут действительно нужно сделать — это вычислить вектор от игрока к позиции на земле прямо под предметом. Это легко сделать, если у вектора, являющегося результатом вычитания, установить координату Y в ноль:-
var heading = target.position - player.position; heading.y = 0; // This is the overground heading.
Понимание векторной арифметики
Векторная арифметика — основа 3D графики, физики и анимации, и, для получения максимальной отдачи от Unity, весьма полезно досконально разбираться в этой теме. Ниже приведены описания основных операций и несколько советов о том, для чего они могут быть использованы.
Сложение
При сложении 2 векторов результат эквивалентен тому, что получится если исходные векторы принять за следующие друг за другом “шаги”. Заметьте, что порядок двух слагаемых не важен, т.к. в любом случае результат будет одинаковый.
Если первый вектор принять за точку в пространстве, то второй вектор можно интерпретировать как сдвиг или “прыжок” из этой точки. Например, чтобы для поиска точки 5-тью единицами выше точки на земле, вы могли бы использовать следующий расчёт:-
var pointInAir = pointOnGround + new Vector3(0, 5, 0);
Если векторы представляют собой силы, то более естественно будет думать о них с точки зрения их направления и величины (величина определяет мощность силы). Сложение двух векторов силы в результате даёт новый вектор, эквивалентный комбинации этих сил. Этот концепт зачастую очень полезен при применении сил с различными раздельными компонентами, которые работают одновременно (например, на летящую вперёд ракету может влиять встречный или боковой ветер).
Вычитание
Вычитание векторов чаще всего используется, чтобы узнать расстояние и направление одного объекта относительно другого. Заметьте, что при вычитании порядок параметров имеет значение:-
// The vector d has the same magnitude as c but points in the opposite direction. var c = b - a; var d = a - b;
Как и с обычными числами, прибавление отрицательного вектора — это то же самое, что и вычитание положительного.
// These both give the same result. var c = a - b; var c = a + -b;
Отрицательный вектор имеет ту же величину, что и исходный вектор, и лежит на той же прямой, только в обратном направлении.
Скалярные умножение и деление
Говоря о векторах, в порядке вещей обращаться к обычным числам (например, значениям типа float) как к скалярам. Это значит, что у них есть только “размер” или величина, в то время как у векторов есть и величина и направление.
Умножение вектора на скаляр даёт в результате вектор, с тем же направлением, что и исходный вектор. Тем не менее, величина нового вектора равна исходной величине умноженной на скалярное значение.
Аналогично, скалярное деление делит исходную величину вектора на скаляр.
Эти операции полезны, когда вектор представляет из себя смещение движения или силу. Они позволяют вам изменить величину вектора без влияния на его направление.
Когда любой вектор делится на собственную величину, то в результате получается вектор величиной 1, известный как нормированный (единичный) вектор. Если нормированный вектор умножить на скаляр, то величина результата будет равна значению скаляра. Это полезно, когда направление силы постоянно, а величина — нет (например, сила от колеса автомобиля всегда толкает вперёд, но её мощность контролируется водителем).
Скалярное произведение (Dot Product)
Скалярное произведение получает 2 вектора и возвращает скаляр. Этот скаляр равен произведению величин этих векторов, умноженному на косинус угла между ними. Когда оба вектора — нормированные, косинус по сути дела утверждает, как далеко первый вектор простирается в направлении второго (или наоборот — порядок параметров роли не играет).
Если работать с точки зрения углов, то можно достаточно просто найти соответствующие косинусы используя калькулятор. Тем не менее, полезно иметь интуитивное понимание основных значений косинуса, как показано на диаграмме ниже:-
Скалярное произведение — это очень простая операция, которую, при некоторых обстоятельствах, можно использовать вместо функции Mathf.Cos или операции векторных величин (оно не делает в точности то же самое, но иногда эффект получается одинаковый). Тем не менее, вычисление скалярного произведения на уровне процессора проходит значительно быстрее, так что оно может оказаться ценной оптимизацией.
Векторное произведение (Cross Product)
Другие операции предназначены для 2D или 3D векторов и для действительных векторов с любым числом измерений. Векторное произведение же, напротив, имеет смысл применять только для 3D векторов. Оно использует 2 вектора как входную информацию и возвращает ещё один вектор в качестве результата.
Итоговый вектор перпендикулярен двум исходным векторам. Можно использовать “правило левой руки”, чтобы запомнить направление выходного вектора относительно исходных векторов. Если первый параметр совпадает с большим пальцем руки, а второй параметр с указательным пальцем, то результат будет указывать в направлении среднего пальца. Если использовать обратный порядок параметров, то тогда итоговый вектор будет указывать в противоположном направлении, но его величина не изменится.
Величина результата равна произведению величин исходных векторов, умноженному на синус угла между ними. Некоторые полезные значения функции синуса указаны ниже:-
Векторное произведение может выглядеть сложным, т.к. оно включает в себя сразу несколько полезных частей информации в возвращённой величине. Тем не менее, как и скалярное произведение, оно очень эффективно с математической точки зрения и может быть использовано для оптимизации кода, который иначе будет зависеть от медленных и сложных функций.
Нормальные векторы поверхностей и вершин
Каждая грань в сетке имеет вектор-нормаль перпендикулярной единицы. Направление вектора определяется порядком определения вершин и типом системы координат (правосторонняя или левосторонняя).
Перпендикулярный вектор нормы единицы для переднего лица
Каждая грань в сетке имеет вектор-нормаль перпендикулярной единицы. Направление вектора определяется порядком определения вершин и типом системы координат (правосторонняя или левосторонняя). Нормаль поверхности направлена от передней стороны поверхности. В Direct3D видима только передняя сторона поверхности. Передняя поверхность — та, на которой вершины определяются по часовой стрелке.
На следующей иллюстрации показан нормальный вектор для передней поверхности.
Выбраковка лиц назад
Любая не передняя поверхность считается задней. Direct3D не всегда отрисовывает задние поверхности, и они отбрасываются. Отбрасывание задних поверхностей означает, что они не отрисовываются. При необходимости можно изменить режим отбрасывания и включить отрисовку задних поверхностей. Подробнее см. в разделе Состояние отбрасывания.
Единичные нормали вершин
Direct3D использует единичные нормали вершин для затенения по методу Гуро, освещения и эффектов материалов.
На следующей иллюстрации показаны нормали вершин.
При применении затенения по методу Гуро к полигону Direct3D использует нормали вершин для вычисления угла между источником света и поверхностью. При этом Direct3D вычисляет значения цвета и интенсивности для вершин и интерполирует их для каждой точки на всех поверхностях примитива. Direct3D вычисляет значение интенсивности света с помощью угла. Чем больше угол, тем меньше света попадает на поверхность.
Плоские поверхности
Если вы создаете плоский объект, настройте нормали вершин перпендикулярно поверхности.
На следующей иллюстрации показана плоская поверхность, составленная из двух треугольников с нормалями вершин.
Плавное затенение на неплоском объекте
Наиболее вероятно, что ваш объект не плоский и состоит из полос треугольников, и треугольники находятся в разных плоскостях. Существует простой способ добиться плавного затенения всех треугольников в полосе. Для этого нужно сначала рассчитать нормальный вектор поверхности для каждой полигональной поверхности, с которой связана вершина. Нормаль вершины можно настроить так, чтобы она образовывала равный угол с каждой нормалью поверхности. Однако этот метод может оказаться недостаточно эффективным для сложных примитивов.
Этот метод проиллюстрирован на следующей схеме, где показаны две поверхности, S1 и S2, с видом на их ребра сверху. Нормальные векторы поверхностей S1 и S2 показаны синим цветом. Нормальный вектор вершины показан красным цветом. Угол между нормальным вектором вершины и нормалью поверхности S1 идентичен углу между нормальную вершины и нормалью поверхности S2. При освещении и затенении этих двух поверхностей по методу Гуро между ними получается плавно затененное и закругленное ребро.
На следующей иллюстрации показаны две поверхности (S1 и S2), их нормальные векторы и нормальный вектор вершины.
Если нормаль вершины тяготеет в одной из поверхностей, с которой она связана, интенсивность света для точек на этой поверхности увеличивается или уменьшается в зависимости от угла между нормалью и источником света. На следующей схеме приведен пример. Поверхности изображены с видом на их ребра. Нормаль вершины тяготеет к поверхности S1, поэтому угол между ней и источником света меньше, чем в том случае, если бы углы между нормалью вершины и нормалями поверхностей были равны.
На следующей схеме показаны две поверхности (S1 и S2) с вектором нормали вершины, тяготеющим к одной из поверхностей.
Острые края
Вы можете использовать затенение по методу Гуро для отображения некоторых объектов с острыми ребрами в трехмерной сцене. Для этого продублируйте нормальные векторы вершин на любом пересечении поверхностей, где требуется острое ребро.
На следующей иллюстрации показаны дублированные нормальные векторы вершин на острых ребрах.
Если вы используете методы DrawPrimitive для отрисовки сцены, определите объект с острыми ребрами как список треугольников, а не полосу треугольников. Если объект определен как полоса треугольников, Direct3D считает его одним многоугольником, состоящим из нескольких треугольных поверхностей. Затенение по методу Гуро применяется как для каждой поверхности многоугольника, так и между смежными поверхностями.
В результате получается объект, плавно затененный от поверхности к поверхности. Поскольку список треугольников — это многоугольник, состоящий из ряда несвязанных треугольных поверхностей, Direct3D применяет затенение по методу Гуро к каждой поверхности многоугольника. Однако оно не применяется от поверхности к поверхности. Если два или более треугольников в списке треугольников смежные, между ними видится острое ребро.
В качестве альтернативы можно перейти к плоскому затенению при отрисовке объектов с острыми ребрами. С точки зрения вычислений, это самый эффективный метод, но при его использовании некоторые объекты в сцене могут быть отрисованы не так реалистично, как объекты, затененные по методу Гуро.
Векторы в C++: для начинающих
Всем привет! До этого дня мы использовали чистые массивы. Чистые — это значит простые массивы, не имеющие у себя в багаже различных функций. В этом уроке мы пройдем нечистые массивы — векторы.
Что такое вектор (vector)
Вектор — это структура данных, которая уже является моделью динамического массива.
Давайте вспомним о том, что для создания динамического массива (вручную) нам нужно пользоваться конструктором new и вдобавок указателями. Но в случае с векторами всего этого делать не нужно. Вообще, по стандарту пользоваться динамическим массивом через конструктор new — не есть правильно. Так как в компьютере могут происходить различные утечки памяти.
Как создать вектор (vector) в C++
Сначала для создания вектора нам понадобится подключить библиотеку — , в ней хранится шаблон вектора.
#include
Кстати, сейчас и в будущем мы будем использовать именно шаблон вектора. Например, очередь или стек, не созданные с помощью массива или вектора, тоже являются шаблонными.
Далее, чтобы объявить вектор, нужно пользоваться конструкцией ниже:
vector тип данных > имя вектора>;
- Вначале пишем слово vector .
- Далее в угольных скобках указываем тип, которым будем заполнять ячейки.
- И в самом конце указываем имя вектора.
vector string> ivector;
В примере выше мы создали вектор строк.
Кстати, заполнить вектор можно еще при инициализации (другие способы мы пройдем позже — в методах вектора). Делается это также просто, как и в массивах. Вот так:
vectorint> ivector = элемент[0]>, элемент[1]>, элемент[2]>>;
После имени вектора ставим знак равенства и скобки, в которых через пробел указываем значение элементов.
Такой способ инициализации можно использовать только начиная с C++11!
Так, чтобы заполнить вектор строками, нам нужно использовать кавычки — «строка» .
Второй способ обратиться к ячейке
Мы знаем, что в векторе для обращения к ячейке используются индексы. Обычно мы их используем совместно с квадратными скобками [] .
Но в C++ есть еще один способ это сделать благодаря функции — at(). В скобках мы должны указать индекс той ячейки, к которой нужно обратиться.
Вот как она работает на практике:
vector int> ivector = 1, 2, 3>; ivector.at(1) = 5; // изменили значение второго элемента cout . at(1); // вывели его на экран
Давайте запустим эту программу:
5 Process returned 0 (0x0) execution time : 0.010 s Press any key to continue.
Как указать количество ячеек для вектора
Указывать размер вектора можно по-разному. Можно это сделать еще при его инициализации, а можно хоть в самом конце программы. Вот, например, способ указать длину вектора на старте:
vector int> vector_first(5);
Так в круглых скобках () после имени вектора указываем первоначальную длину. А вот второй способ:
vector int> vector_second; // создали вектор vector_second.reserve(5); // указали число ячеек
Первая строчка нам уже знакома. А вот во второй присутствует незнакомое слово — reserve , это функция, с помощью которой мы говорим компилятору, какое количество ячеек нам нужно использовать.
Вы можете задать логичный вопрос: “А в чем разница?“. Давайте создадим два вектора и по-разному укажем их количество ячеек.
#include #include // подключили библиотеку using namespace std; int main() setlocale(0, ""); vector int> vector_first(3); // объявили // два vector int> vector_second; // вектора vector_second.reserve(3); cout <"Значения первого вектора (с помощью скобок): "; for (int i = 0; i 3; i++) cout [ i] <" "; > cout <"Значения второго вектора (с помощью reserve): " ; for (int i = 0; i 3; i++) cout [ i] <" "; > system("pause"); return 0; >
Значения первого вектора (с помощью скобок): 0 0 0 Значения второго вектора (с помощью reserve): 17 0 0 Process returned 0 (0x0) execution time : 0.010 s Press any key to continue.
Как видим, в первом случае мы вывели три нуля, а во втором: 17, 0, 0.
Все потому, что при использовании первого способа все ячейки автоматически заполнились нулями.
При объявлении чего-либо (массива, вектора, переменной и т.д) мы выделяем определенное количество ячеек памяти, в которых уже хранится ненужный для ПК мусор. В нашем случае этим мусором являются числа.
Поэтому, когда мы вывели второй вектор, в нем уже находились какие-то рандомные числа — 17, 0, 0. Обычно они намного больше. Можете кстати попробовать создать переменную и вывести ее значение.
Нужно помнить! При использовании второго способа есть некоторый плюс — по времени. Так как для первого способа компилятор тратит время, чтобы заполнить все ячейки нулями.
Как сравнить два вектора
Если в середине программы нам понадобится сравнить два массива, мы, конечно, используем цикл for и поочередно проверим все элементы.
Вектор опять на шаг впереди! Чтобы нам сравнить два вектора, потребуется применить всего лишь оператор ветвления if.
if (vec_first == vec_second) // сравнили! cout <"Они равны!"; > else cout <"Они не равны"; >
Конечно, компилятор все равно прогонит эти два вектора по циклу, проверяя ячейки. Но оцените, насколько благодаря этому программа стала компактнее. Разве это не прекрасно?
Вот так бы выглядела программа выше, если бы мы не использовали знак равенства для векторов.
bool flag = true; if (vec_first.size() == vec_second.size()) for (int i = 0; i vec_first.size(); i++) if (vec_first[i] != vec_second[i]) cout <"Они не равны!"; flag = false; break; // выходим из цикла > > > else flag = false; cout <"Они не равны!"; > if (flag) cout <"Они равны!"; >
- Сначала мы создали булеву переменную flag равную true . У нее задача такая:
- Если в условии (строки 5 — 10) она станет равна false — то значит эти векторы не равны и условие (строки 14 — 16) не будет выполняться.
- Если же она после цикла (строки 3 — 12) останется равна true — то в условии (строки 14 — 16) мы сообщим пользователю, что они равны.
- В условии (строка 3) проверяем размеры двух векторов на равенство.
- И если условие (строки 5 — 10) будет равно true — то мы сообщим пользователю, что эти два вектора не равны.
Как создать вектор векторов
Понятно, что вам может понадобиться записать числа в двумерный массив. Но зачем использовать массив, если можно оперировать векторами.
Сейчас вы узнаете, как создать вектор векторов или простым языком массив векторов.
vector vector тип данных > >;
Как можно увидеть, нам пришлось только добавить слова vector и еще его .
А чтобы указать количество векторов в векторе, нам потребуется метод resize() .
vector vector int> > vec; vec.resize(10); // десять векторов
Но есть еще одни способ добавления векторов в вектор. Для этого способа мы будем использовать функцию push_back() (читайте ниже, что она делает).
vec.push_back(vector int>());
- В аргументах функции push_back() находится имя контейнера, который мы хотим добавить. В нашем случае — vector .
- А дальше идет тип контейнера — .
- И все заканчивается отрывающей и закрывающей скобкой () .
Для двумерного вектора тоже можно указать значения еще при инициализации:
vector vector int> > ivector = 1, 4, 7>, 2, 5, 8>, 3, 6, 9>>;
— это значения элементов первого массива (первого слоя). Такие блоки значений, как , должны разделяться запятыми.
Методы для векторов:
Сейчас мы разберем некоторые методы, которые часто используются вместе с векторами. Метод — это функция, которая относится к определенному STL контейнеру.
В нашем случае этим STL контейнером является вектор. Если вы дальше собираетесь оперировать векторами — лучше все перечисленные функции запомнить.
Если нам требуется узнать длину вектора, понадобится функция — size() . Эта функция практически всегда используется вместе с циклом for.
for (int i = 0; i ivector.size(); i++) // . >
Также, если нам требуется узнать пуст ли стек, мы можем использовать функцию — empty() .
- При отсутствии в ячейках какого-либо значения это функция возвратит — true .
- В противном случае результатом будет — false .
Вот пример с ее использованием:
if (ivector.empty()) // . >
2) push_back() и pop_back()
Как мы сказали выше, у векторов имеются методы, которые помогают оптимизировать и улучшить жизнь прог