Всем привет.
3Д очень интерестно, динамично, красиво и сложно! Как подступится если математика не является сильной стороной (как в моем случае)? Никак! Но всегда можно начать "покуривать" статьи в данном направлении.
Цель статьи: много примеров, немного кода, минимум математики.
Для примеров буду использовать библиотеку System.Numerics.
Заранее прошу прощения за топорность примеров и решений у всех математиков которые не нароком увидят данную статью, это было сделанно не за ради насмешки, а ради простоты и наглядности.
Вектор или точка вот в чем вопрос...
Само понятие Вектор звучит как - направленный отрезок в пространстве, исходя из определенния он должен иметь начало и конец в виде точек в пространстве от куда и куда он направлен. Определение точки смутноватое, ее назыает абстрактным обьектом и еще подобными обозначениями, для работы это описание "ни о чем".
Определения выше блиски к математическим, и не всегда понятно к каким больным местам их приложить. Для начала давайте рассмотрим что нам предлагагают библиотеки.
Рассмотрим несколько примеров:
WPF нам предоставляет Vector3D/Vector2D
и Point3D/Point2D
System.Numerics Vector3/Vector2
и ... и все, нет обьекта точки ну и ... ладно.
Например SharpDX имеет тоже Vector3/Vector2
.
Ну вы поняли,
eсли возьмете любую другую библиотеку будут похожие струткуры.
Струтура обьектов похожая { X; Y; Z; } в зависимости 3D/2D разница только в значениях с плавающей зяпятой WPF(double) System.Numerics(float).
Рассмотрим подробнее System.Numerics Vector3
, и почему нет point структуры.
P.S. Я показал структуры WPF для примера, мы не будем разбирать подходы в WPF, аналогии провести будет не сложно если понадобится.
СтрутуройVector3
описывается vector и point, дело в том что и vector и point это всеровно вектора, только точка в пространстве это вектор от {0,0,0} -> {x,y,z}
т.е. точка это вектор который показыват как добраться до точки из начала координат, а вектор {startX, startY, startZ} -> {endX, endY, endZ}
, по этому не имеет смысла иметь две струтуры как сделали в WPF, возможно только ради семантики.
Из выше сказанного предлогаю выделить упрощенные понятия.
- Точка или Вектор это обьекты имееющие координаты, направление и длину.
Упростим струтуру до минимума чтобы не заморачиватся :)
struct Vector3 { float X; float Y; float Z; Vector3(float x, float y, float z){ ... } }
Эта структурка будет описывать вектор в примерах..
Элементарные операции
Структуры имеют перегрузки математических операций [-,+,/,*] над Vector3
, я не буду особенно углубляться в них, просто берем и используем.
Определим Vector3
как точку var point = new Vector3(10,10,10)
, данная точка находиться на удалении от мирового центра на растоянии 10 по всем осям.
Проверим утверждение что точка это вектор от var centerWorld = new Vector3(0,0,0)
координаты.
Зная 2 точки(вектора) построим новый вектор, используя одну их элементарных операций вычитание/сложение векторов, point2 - point1
даст нам вектор смотрящий по направлению из point1 -> point2 и имеет длину равную растоянию между этим точками. Докажем выше оговоренное утверждение.
var vector = point - centerWorld; // (x - 0, y - 0, z - 0) vector == point //-> true //это доказывает что точка, это всего лишь вектор от начала координат
Вычитание/Сложение векторов
Важно не путать что вычитание и сложение векторов рассчитываются по разным правилам. Сложение - использует правило параллелограмма или треугольника, а вычитание правило треугольника и результат вектор направленый в сторону вычитаемого.

Следовательно чтобы определить вектор от одной точки к другой мы используем операцию вычитания. Подробней можно почитать на вики
Определим 2 отрезка {point1;poin2}, {point1;poin3} и создадим 2 вектора в пространстве, для последующий задач.
var poin1 = new Vector3(...); var poin2 = new Vector3(...); var poin3 = new Vector3(...); var vector1 = point2 - point1; var vector2 = poin3 - point1;Выглядит как-то так.
Что мы можем сделать с векторами?
Первое что надо рассмотреть это нормализация вектора, это когда вектор становится единичной длины, а для нас это значит что он просто определяет направление. Нормализованые вектора нам дают возможность делать манипуляции(перемещения) на заданые значения по направлению вектора.
Расмотрим пример перемещения точки по нормализованому вектору.
Задача: Необходимо найти точку расположеную по центру между двух других т.е на векторе vector1.
var normal = vector1; normal.Normalize(); // делаем вектор нормализированым var centerPoint = /* берем точку старта вектора */ point1 /* говорим точке двигатся по вектору*/ + normal /* говорим на какое растояние по вектору нужно сдвинутся*/ * vector1.Length()/2;
Тут надо четко понимать, все что нам дает vector1 это направление и длину отрезка {point1;point2} но где этот орезок находится в пространстве мы не знаем, для этого нам необходимы точки начала или конца отрезка для выяснения. Зачем нам нужен был нормализованый вектор? Дело в том что если вектор не нормализованый он содежит значение длины, и если мы попробуем переместить какую-то точку по данному вектору то он сдвинется на длину вектора по его направлению, а наша задача сдвинуть точку на конкретное расстояние, для этих целей мы используем нормализовный вектор чтобы он "не имел длины".
Скалярное произведение (Dot product) & Векторное произведение (Cross product)
Начнем с Dot
, я не буду приводить стандартное определение, т.к. оно легко гуглится.
float Dot(Vector3 left, Vector3 right);
Простыми словами скалярное произведение векторов - проэкция одного вектора на другой, float
это "скаляр" длина вектора проекции, также через это значение можно выразить угол между векторами (это будет немного позже).
var normal = vector2; normal.Normalize(); var scalar = Vector3.Dot(vector1, normal);
Важно! Вектор на который вы хотите получить проекцию должен быть нормализованый, иначе Dot
вернет не тот результат который вы ожидали. Еще один важный момент, если оба вектора не нормализированыев перена мест векторов в операции будет иметь значение, это значит Dot(vector1, vector2) != Dot(vetor2, vector1)
Результатом векторного произведения Cross
есть вектор который будет перпендикулярен обоим векторам над которыми проводилась операция. Тут надо уточнить, может быть не один перпендикулятный вектор к двум другим, как это описывается математически можно почитать здесь. Нам надо понимать какие данные нам надо дать на вход чтобы получить ожидаемый результат на выходе.
Расмотрим простой пример, нам нужно найти ось плоского круга в пространстве т.е. нормаль к плоскости данного круга.
// входные данные var circlePoints = new Vector3[...]; // для начала найдем центр круга var center = Vector3.Zero; for(var i = 0; i < circlePoints.Length; i++) { var point = points[i]; center = Vector3.Add(point, center); // Add тоже самое что (point + center) } var center = center / circlePoints.Length; // найдем 2 вектора от центра к случайным точкам круга var v1 = circlePoints[0] - circleCenter; v1.Normalize(); var v2 = circlePoints[circlePoints.Length/3] - circleCenter; // проверим что вектора не коллинеарны var dot = Vector3.Dot(v1,v2); if(dot == 1f || dot == -1f){ return; // вектора коллинеарны, если результат 2х нормализированый веторов (т.е. векторов с длиной 1) равны // -1 = противоположно направлены // 1 = сонаправлены } // и наш перпендикуляр к плоскости круга var circleNormal = Vector3.Cross(v1, v2); circleNormal.Normalize();

Надеюсь в этом примере все понятно.
Рассмотрим еще один по сложнее. Пользователь хочет мышкой вращать обькет в нашей 3Д сцене, он хватает обьект мышью и ведет ее по какому-то направлению ожидая, что обьект повернется за курсором.
// входные данные var objectCenter = new Vector3(...); // геометрический центр обьекта var mouseCapturedPoint = new Vector3(...); // точка захватат мышкой обьект var mouseMovedPoint = new Vector3(...); // точка куда пользователь сдвинул мышь //строим два вектора var startVector = mouseCapturedPoint - objectCenter; var endVector = mouseMovedPoint - objectCenter; // найдем угол поворота startVector.Normalize(); var dot = Vector3.Dot(startVector,endVector); var angleRadian = Math.Acos(dot); // найдем ось во круг которой надо повернуть обьект var rotationAxis = Vector3.Cross(startVector, endVector); rotationAxis.Normalize();
Выше мы посчитали все данные нужные для поворота обьекта, с использованием Cross/Dot. Мы получили ось вокруг которой надо повернуть и угол, осталось только посчитать трастформацию поворота, но работа с матрицами вне данной статьи.
И последний пример. Пользователь который хотел поворачивать обьект теперь хочет его передвигать строго в доль осей координат, например как в других 3Д редакторах можно схватить за стрелку какой-то оси и тянув передвигать обьект.
//входные данные var axis = Vector3.UnitZ; // ось в доль которой мы будем двигать var objectCenter = new Vector3(...); var mouseCapturedPoint = new Vector3(...); var mouseMovedPoint = new Vector3(...); //строим вектор перемещения var moveVector = mouseMovedPoint - mouseCapturedPoint; // находим проекцию на ось var dot = Vector3.Dot(axis,moveVector); //новый центр куда надо переместить обьет var newCenter = objectCenter + axis * dot;
В данном случае какое бы движение пользователь не делал в результате всегда будет движение по оси Z.
И в завершении статьи тонкости работы с Vector3.Dot + Math.Acos
т.к. результат дота это float
значение с плавающей запятой то можно нарватся на проблему погрешности и получить например вместо ожидаемого 1
чтото типа 1.000000000001
и тогда Acos вернет NaN.
Есть еще один вариант посчитать угол используя Dot & Cross.
var angleRadian = Math.Atan2( Vector3.Cross(Vector3.UnitX,-Vector3.UnitX).Length(), Vector3.Dot(Vector3.UnitX,-Vector3.UnitX)); // результатом будет 3,14 или 180 градусов
angleRadian это угол между векторами направленными вдоль осей +X и -X.
Note: Можно встретить фразу "перевести точки в локальну координатую систему чего-то", зная все выше оговоренное в статье попробуем обьяснить.
//точка в глобальной системе координат, //а также вектор из Vector3.Zero -> Vector3(10,10,10) var pointWorld = new Vector3(10,10,10); //построим вектор от этой точки pointWorld-> Vector3(..,..,..) var vector = = new Vector3(..,..,..) - pointWorld;
Если мы рассмотрим pointWorld не как точку в глобальной системе, а как центр нашей локальной системы координат, тогда vector
это localCenter -> Vector3(..,..,..)
и он является точкой в системе координат с центом в pointWorld, и он является точкой в системе координат pointWorld.
Добавлю еще отличное видео по данной теме Векторы, что это такое? и на этом все.
Пишите что Вам понравилось или не понравилось в комментариях. Eсли Вы нашли ошибки в статье напишите об этом пожалуйста чтобы я мог исправить и не вводить ни кого в заблуждение.
Всем спасибо!