DirectX Tutorials Уроки по DirectX

Object Picking © Gary Simmons
Перевод на русский язык © Яценко Борис и Бутко Платон, 2000

Заметьте, что этот учебник написан для работы с перспективной проекцией

Object Picking это процесс выделения обьекта или многоугольника мышью. Есть много способов использования этого в 3D приложениях, однако это не так просто как Вам вначале покажется. Этот процесс включает выбор многоугольников с использованием мыши и эта статья Вам подскажет как конвертировать экранные координаты в мировые. Прежде всего, чтобы решить эту проблему, мы должны разделить нашу задачу на этапы:

  1. Конвертируем экранные координаты мыши в мировые координаты
  2. Проведем луч из камеры к этим мировым координатам
  3. Проверяем подходит ли луч к каждому из обьектов в пространстве
  4. Проверяем каждый многоугольник на предмет пересечения луча

Первая проблема, которую нужно решить будет конвертирование экранных координат мыши в мировые. Чтобы сделать это мы вначале должны подробно рассмотреть ViewPort (Область Просмотра) и Viewing Frustum (Пирамида Просмотра).

Обратная проекция

Прежде всего давайте предположим, что разрешающая способность у нас 640x480. Не бойтесь, не имеет значение какое разрешение экрана у Вас в действительности, мы используем это разрешение для примера.

Когда Direct 3D конвертирует точку из мировых координат в экранные, требуются следующие шаги:

World Space -> View Space -> Viewport Space -> Screen Space

Мы должны сделать этот процесс в обратном порядке. Это означает, что первое конвертирование должно быть из Screen Space (Пространство Экрана)в Viewport Space (Область Просмотра). Viewport Space использует нормализованные координаты. Это означает, что вместо X координат, измеряемых в ( 640x480) в пределах от 0 до 640 точка находиться в пределах от -1 до +1. Это же справедливо для Y координат. Это означает, что мы должны масштабировать координаты нашей мыши в пределах от -1 до +1 как для оси X так и для оси Y. Есть еще и другое различие. Начало Screen space находится в верхнем левом углу экрана а начало ViewPort space находиться в центре экрана. Мы можем конвертировать координаты мыши в пределы -1,+1 и передвинуть "точку отсчета" в центр экрана с помощью следующих расчетов:

float NMouseX=1.0 - 2.0*mousex/640;
float NMouseY=1.0 - 2.0*mousey/480;

Чтобы понять как это работает, представим, что центр экрана находится в точке 320. Давайте посмотрим следующую формулу.

1.0-2.0*(320/640=0.5)=
1.0-2.0*0.5=
1.0-1.0=0.0;

Как видите это работает.

Хорошо, сейчас мы имеем координаты мыши в пределе -1,+1 но что нам от этого? Прежде всего, мы вспомним тригонометрию. Посмотрите на диаграмму ниже.

Вид Frustum странная штука. Заметим из вышесказанного что по мере увеличения дистанции от камеры увеличивается поле видимости. К примеру красная линия B находится на расстоянии 1.0 от камеры. Голубая точка представляет относительно максимальное расстояние Y от камеры которое попадает в поле зрения на расстоянии 1.0 от камеры. Когда это воспроизводиться на экране, это будет находиться на самом верху дисплея, что либо выше будет обрезанно.Сейчас посмотрите на красную линию D. Она представляет расстояние 2.0 от камеры и голубая точка представляет максимальное положение Y которое попадает в поле зрения на расстоянии 2.0 от камеры. Сейчас очевидно, что вторая голубая точка имеет более высокую Y координату чем первая точка. Это максимальная Y координата которая попадает в поле видимости на расстоянии 2.0 от камеры. Проблема тут в том, что обе эти точки воспроизводятся на экране в одинаковой позиции (смущенны?), подумайте об этом, представив что Вы играете в X-Wing и Вы находитесь очень близко к star destroyer, только очень маленькая часть корабля будет видна на экране но когда Вы отлетите от корабля расстояние увеличится и весь корабль будет попадать во Frustum.

Первое, что нам нужно сделать, это вычислить, как далеко от центра экрана находится координата Y мыши. Мы вычисляем вектор идущий из позиции камеры до точки попадания на расстоянии 1.0 от камеры. Это будет направление и угол луча, который будет выброшен из камеры. Когда мы получим этот вектор, мы можем увеличить его длину до любого расстояния, создавая наклон для использования пересечения с многоугольником. Посмотрите ниже на ту же самую диаграмму.

Чтобы увидеть, как это работает, мы вычислили вектор, от позиции камеры до попадания мыши на расстояние 1.0. Если у нас есть это вектор, мы можем умножить его на любое расстояние, на которое мы хотим продлить луч. Мы умножим его на 2 чтобы получить спроектированную точку попадания мыши на дистанции 2.0. Вы видите, что луч точно пересечет многоугольник на расстоянии 1.0 и 2.0, даже когда могут быть разными положения Y в мировом пространстве. Помните, что они займут одно и то же место на экране. Но как получить этот вектор? Оказывается, что следующая формула вычисляет относительное максимальное положение Y от камеры, которое попадает на экран на расстоянии 1.0.

MaxY=tan(FOV / 2)

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

MaxY=2.0*tan(FOV / 2)

Все, что нужно сделать, это увеличить операцию тангенса на расстояние, на которое мы хотим вычислить максимум Y, но нам нужно его знать на расстоянии 1.0. Необходимо хорошо запомнить, что наши координаты мыши были помещены в центре экрана в пределы [-1,+1], это может быть использованно для нахождения координат мыши, как % от максимального Y (1.0=100%=вверх экрана). Мы делаем это так:

D3DXVECTOR3 cameraspace;
cameraspace.y=(NMouseY*tan(FOV / 2.0));

В этой точке мы сейчас имеем координату Y попадания мыши на расстояние 1.0 от камеры. Теперь нам нужно найти положение Х мыши на расстоянии 1.0. Делаем тоже самое, но с 2 различиями. Мы должны инвертировать нашу нормализованную Х координату мыши, потому что в данный момент неверно округлять до +1 слева и -1 справа. Во вторых мы должны разделить это значение на значение соотношение экранных размеров которое Вы используете. Соотношение экранных размеров (aspectratio) вычисляется делением ширины экрана на его высоту (640/480 =1.333333). В любом случае мы получаем Х координату так:

cameraspace.x=((-NMouseX/aspectratio)*tan(FOV/2.0));
cameraspace.z=1.0;

Мы сейчас конвертировали положение Х и У мыши в пространство камеры на дистанцию 1.0 от камеры. Сейчас мы должны провести наш луч, он определяется начальной и конечной точкой. Мы уже знаем начальную точку - это положение нашей камеры. Конечной точкой будет наши вновь вычисленные Х и У, увеличенные на длину луча и конвертированные в World Space. Вы можете продлить луч на столько, сколько хотите. Ниже мы используем длину луча 200. Любые обьекты, лежащие дальше, будут игнорироваться, так как они будут слишком маленькие. Вы можете сделать длину вашего луча, равной расстоянию до отсекающей плоскости, если хотите, чтобы выделяемые обьекты были маленькие.

D3DXVECTOR3 LineEnd;
LineEnd.x=cameraspace.x*200; // (200=length of ray)
LineEnd.y=cameraspace.y*200;
LineEnd.z=200;

Помните, что у нас длина луча равна 1. Поэтому, чтобы продлить на большее расстояние, мы должны умножить Х и У на желаемое расстояние и установить Z равной этому расстоянию. Мы теперь знаем конечную точку и можем конвертировать эту точку в World Space трансформируя ее при помощи инверсной матрицы просмотра.

D3DXMATRIX matView;
float det;
lpDevice->GetTransform(D3DTRANSFORMSTATE_VIEW,(D3DMATRIX *)&matView);
D3DXMatrixInverse(&matView,&det,&matView);
D3DXVec3TransformCoord(&LineEnd,&vec,&matView);
D3DXVECTOR3 LineStart=CameraPosition;

Начальной точкой будет позиция камеры.

D3DXVECTOR3 MGM_MakeRay(float FOV,float AspectRatio,WORD MouseX,WORD MouseY,float range)
{
D3DXVECTOR3 LineEnd,CameraSpacePos;
float NMouseX,NMouseY,det;
D3DXMATRIX matView;
NMouseX=1.0 - 2.0*MouseX/640;
NMouseY=1.0 - 2.0*MouseY/480;
//is FOV in radians
CameraSpacePos.y=(NMouseY*tan(FOV/2.0));
CameraSpacePos.x=((-NMouseX/AspectRatio)*tan(FOV/2.0));
LineEnd.x=range*CameraSpacePos.x;
LineEnd.y=range*CameraSpacePos.y;
LineEnd.z=range;
lpDevice->GetTransform(D3DTRANSFORMSTATE_VIEW,(D3DMATRIX*)&matView);
D3DXMatrixInverse(&matView,&det,&matView);
D3DXVec3TransformCoord(&LineEnd,&LineEnd,&matView);
return LineEnd;
}

Эта функция возвращает конец луча в мировых координатах на расстоянии дистанции от камеры. Помните, что начальная точка это позиция камеры.

D3DXVECTOR3 rayend,raystart;
raystart=CameraPosition;
rayend=MGM_MakeRay( fov , 1.333333, mousex , mousey, 200);

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

Скалярное произведение векторов

В отличие от векторного произведения, скалярное произведение не возвращает третий вектор, оно возвращает скалярное произведение - число. Но что это число дает нам? В нашем случае это значение - косинус угла между 2 векторами, увеличенное на длину векторов.

Это можно записать так ( Примечание: V1, V2 оба векторы |V1|, |V2| возвращает длину вектора):

Cos_angle=DotProduct(V1,V2) / |V1| * |V2|

Другими словами, чтобы найти косинус угла между V1 и V2 мы должны сначала выполнить их скалярное произведение и разделить результат на длину V1, умноженную на длину V2.

Длина векторов высчитывается так (помните, что V1 - вектор):

VectorLength=SQRT( (V1.x*V1.x) + (V1.y*V1.y) + (V1.z*v1.z));

Заметьте, что нахождение косинуса угла даже проще, если оба V1 и V2 единичные вектора (длина обоих =1). В этом случае мы можем обойтись без масштабирования длины векторов, потому что результат V1*V2 всегда будет равен единице. Так, если мы имеем 2 единичных вектора, мы можем переписать вышестоящую формулу так:

Нахождение косинуса угла с 2 единичными векторами

Cos_angle=DotProduct(V1,V2);

Когда мы имеем Cos_angle (косинус угла), мы можем найти действительный угол между 2 векторами с помощью инвертирования косинуса (или Arc Cosine) функции. Эта функция берет косинус угла и возвращает действительный угол в радианах.

Нахождение действительного угла в радианах:

ActualAngle = acos (Cos_angle);

Хорошо, что дальше?

Хорошо, как вычислить скалярное произведение двух векторов V1 & V2 ? Мы делаем это следующим образом:

V2 = V1.x*V2.x + V1.y*V2.y + V1.z * V2.z

В D3DX библиотеке есть такая функция, она называется D3DXVec3Dot и вызывается так:

result=D3DXVec3Dot(&vector1,&vector2);

И так, как скалярное произведение нам поможет в нахождении точки столкновения нашего луча с полигоном?

Нахождение столкновения

Дистанция от точки до плоскости

Одно из достоинств скалярного произведения векторов это то, что результат одного из векторов -это единичный вектор (его длина =1) и другой не единичный вектор. Как мы уже обнаружили, скалярное произведение двух единичных векторов это косинус угла между ними. Если один из векторов не единичный, тогда результатом скалярного произведения будет косинус угла между ними, умноженный на длину второго не единичного вектора (если тяжело понять, смотрите на рисунок):

Вы можете видеть, не единичный вектор V1 образует гипотенузу прямоугольного треугольника и единичный вектор V2 образует часть противоположной стороны треугольника. Вы так же можете подумать, что результат скалярного произведения не только косинус угла, но так же прилегающий катет треугольника. Сейчас представим, что прилегающий катет треугольника это плоскость, в которой лежит многоугольник в координатах (5, 5) на вышестоящем рисунке, это будет любая точка на плоскости (любая вершина, которая находится в многоугольнике). V1 вектор направления из точки вида к вершине с координатами (5, 5). Так же подумайте о векторе V2, как о нормали к плоскости и точка (0, 0) будет положением наблюдателя. Результатом скалярного произведения будет дистанция от плоскости (P1). Это не говорит нам о дистанции до полигона, имейте ввиду. Только дистанция к бесконечной плоскости, в которой лежит многоугольник. Если это расстояние слишком велико, то мы очевидно, то мы очевидно не попали в многоугольник.

Не понятно? Правильность этого мы проверим на математике. Мы можем видеть на простом примере выше, что точка (0, 0) лежит на расстоянии 5 от плоскости (P1). Так же мы можем видеть, что плоскость многоугольника (P1) это расстояние 5 от точки взгляда (0, 0). Посмотрим, дает ли наше скалярное произведение тот же результат.

Мы имеем два таких вектора :

VECTOR v1=(5.0 , 5.0 , 0.0); //Не единичный вектор формирует гипотенузу. Этот вектор описывает направление от точки взгляда viewpoint(0,0) до любой вершины в многоугольнике (5,5). Vertex[0]-viewpos
VECTOR v2=(1.0 , 0.0 , 0.0 ); // Единичный вектор. Это плоскость или нормаль многоугольника
float p1=DotProduct(v1,v2); // p1=дистанция к плоскости

Результат равен 5. Это правильно и это все то, что мы можем сделать, чтобы рассчитать расстояние от плоскости. Почему это правильно?

Если мы получаем длину V1 так,

lenght = sqrt (5*5,0*0,5*5) ; // мы получаем результат 7.0710678.

Это длина вектора V1. Мы можем легко понять по рисунку, что угол который выше, это 45 градусов, потому что он растет в равной степени по каждой оси (Х и У). Но чтобы проверить, 45 ли это градусов, мы можем разделить результат скалярного произведения, который равен 5 на длину единичного вектора. К примеру:

5 / 7.0710678=0.7071067 ; //Тут мы получаем косинус угла
float angle=acos(0.7071067)=0.7853981 ; // 0.78539181 радиан = 45 градусов

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

Скалярное произведение находит косинус угла и умножаем его на длину не единичного вектора v1.

DotProduct=cos_angle=0.7071067(cosine of 45 degrees) * 7.0710678 (length of v1) =5;

Пересечение линии с плоскостью

Так мы знаем, как посчитать дистанцию от точки к плоскости, но как мы проверим, пересекают ли луч или линия нашу плоскость? Мы можем провести луч из любой точки и получить точные координаты пересечения. Нужно знать несколько вещей, прежде чем мы продолжим.

Чтобы определить плоскость в трехмерном пространстве, нам нужно 3 вершины. Нам нужно сконструировать 2 вектора треугольника, которые дадут 2 стороны треугольника и выполним векторное произведение векторов над ними, чтобы получить нормаль к плоскости, в которой они лежат.

Мы можем видеть, что векторы v1,v2 & v3 образуют плоскость. Чтобы вычислить нормаль к плоскости, нам нужно 2 вектора, лежащих в этой плоскости, нам для этого подойдут 2 наши стороны треугольника. Затем мы выполняем векторное произведение на наши 2 вектора (стороны), результатом будет вектор нормали.

Вычисляем нормаль к плоскости:

Vector edge1=v2-v1;
Vector edge3=v3-v1;
Vector Normal=CrossProduct(edge1,edge2);

Когда мы находимся в начале линии (-2, 4) (к примеру, это положение камеры -2, 4) и проводим луч или линию (направление взгляда камеры) в конец линии в координаты конца линии (-4,8). Ради этой демонстрации мы предположим, что мы уже создали нормаль, как описано выше, используя векторное произведение и плоскость, лежащую в 45 градусах в направлении (-х, у) (красная линия -плоскость). Это создаст вектор нормали многоугольника (-0.5, 0.5, 0.0).

С начала мы должны найти расстояние к плоскости от начала линии (позиция камеры), оранжевая линия L3. Мы разобрали, как сделать это раньше, мы находим вектор L1 (розовая линия), умножая линию камеры (-2, 4) на любую точку в многоугольнике (мы в рисунке ее назвали Vertex (8, 2)). Это работает, потому-что все вершины в многоугольнике лежат в одной и той же плоскости, так-что любая вершина нам подойдет. После получения вектора L1 мы можем вычислить скалярное произведение L1 и нормали и нормали плоскости. Это дает нам косинус угла между 2 векторами, умноженный на длину L1. Это дает нам линию L3 (оранжевая линия, которая формирует противоположную сторону треугольника), которая и является расстоянием до плоскости. Но как Вы видите выше, плоскость составляет прилегающую сторону треугольника.

Нахождение дистанции к плоскости:

VECTOR L1=vertex-linestart;
Distance_to_plane=DotProduct(L1,Normal); // =6.0 Это верно, согласно рисунку d.

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

Нахождение длины спроецированной (projected) линии:

VECTOR line_direction=(lineend-linestart); // получаем вектор (6,-12)
float linelength=DotProduct(line_direction,Normal); // длина линии =-9 что является правильным. Проследите пальцем линию на рис.d.)

Сейчас мы разделим расстояние к плоскости на длину линии, чтобы изучить отношение между ними Это даст нам что-то в роде процента в низ по линии, который мы должны пройти, чтобы достичь пересечения. Этот процент находится между 0 и 1. К примеру 0,5 пересечет вашу длину линии *0,5, которая будет на пол пути этой линии вниз. Если результат 1 (свыше 100% длины линии), тогда линия не достигает плоскости. С другой стороны, если результат отрицательный, тогда плоскость находится позади начала линии.

float percentage = - Distance_to_Plane / linelength; // =0.66667 или 66 процентов пути линии вниз.
// Давайте проверим, правда ли это?

intersect.x=linestart.x+line_direction.x*percentage; // = - 2 + 6 * 0.66667 = - 2 + 4= 2(X)
intersect.y=linestart.y+line_direction.y*percentage; // = 4 + - 12 * 0.66667 = 4 + - 8 = -4(Y)
intersect.z=linestart.z_line_direction.z*percentage; // = 0 + 0 * 0.66667 =0 (Z) не используется в этом примере

Точка пересечения находится в ( 2 , -4 , 0 ).

Давайте напишем функцию пересечения луча, используя библиотеку D3DX Library. Следующая функция берет точку начала и конца луча, так-же берет нормаль многоугольника и любую вершину в многоугольнике. Так-же она берет указатель на структуру D3DXVECTOR3 которая будет заполнена результатом.(точка пересечения в мировых координатах). Функция возвращает true если пересечение было, false если небыло. Если произошло пересечение, возвращается указатель на дистанцию float и возвращается дистанция от начала луча (0.0 -1.0).

bool MGM_GetIntersect (D3DXVECTOR3 linestart,D3DXVECTOR3 lineend,D3DXVECTOR3 vertex,D3DXVECTOR3 normal,D3DXVECTOR3 * intersection,float *distance)
{
D3DXVECTOR3 direction,L1;
float linelength,dist_from_plane,percentage;

direction.x=lineend.x-linestart.x; // вычислим вектор направления линий (зеленная линия на рисунке d)
direction.y=lineend.y-linestart.y;
direction.z=lineend.z-linestart.z;
linelength=D3DXVec3Dot(&direction,&normal); // Это дает нам длину линии (голубая точка L3 + L4 на рисунке d)
if (fabsf(linelength)< < 0.00001) // проверяем, не равно ли 0
{
return false; //=0 означает, что линия паралельна плоскости и не может ее пересекать
}
L1.x=vertex.x-linestart.x; // calculate vector L1 (розовая линия на рис. d)
L1.y=vertex.y-linestart.y;
L1.z=vertex.z-linestart.z;
dist_from_plane=D3DXVec3Dot(&L1,&normal); // дает дистанцию от плоскости (оранжевая линия L3 на рис. d)
percentage=dist_from_plane/linelength; // Как далеко пересечение линии в процентах от 0 до 1
if (percentage< 0.0) // Плоскость позади начала линии
{
return false;
}
else
if (percentage > 1.0) // Линия не достигает плоскости
{
return false;
}
*distance=percentage; //Записываем дистанцию от начала луча (0.0 -1.0)
intersection->x=linestart.x+direction.x*percentage; // прибавляем процент линии к линии старта
intersection->y=linestart.y+direction.y*percentage;
intersection->z=linestart.z+direction.z*percentage;
return true;
}

Помните, что результат пересечения хранится в мировых координатах..

Пересечение линии с многоугольником

Но попали ли мы в многоугольник или просто в плоскость?

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

Как мы уже знаем, выполнение скалярного произведения над 2 единичными векторами возвращает косинус угла между ними.

(вид сверху !)

Сначала мы создаем 3 вектора, направленные из точки попадания в каждую вершину. В указанной диаграмме мы в качестве примера используем треугольник, поэтому нам надо 3 вектора L1 , L2 & L3. Сейчас нормализуем эти векторы ( сделаем, чтобы их длина =1) -сделаем их единичными. Сейчас мы над каждым вектором выполним скалярное произведение: скалярное произведение ( L1 , L2 ), скалярное произведение( L2 , L3 ) скалярное произведение( L3 , L1 ). Результатом каждого скалярного произведения будет косинус угла между ними, так выполнение функции, обратной косинусу (acos) (арк. косинус) над результатом каждого скалярного произведения даст угол между ними.

Мы суммируем все 3 угла в цикле (красный, синий и зеленый на диаграмме) если в сумме они равны 360 градусам (круг или 6.28 радиан) тогда точка пересечения находится в многоугольнике. Если суммарный угол меньше 360 градусов, тогда точка попадания лежит вне многоугольника.

Сейчас напишем функцию, которая проверяет, находится ли точка пересечения в пределах многоугольника.

bool MGM_HitPolygon (D3DXVECTOR3 plane_intersection ,D3DXVECTOR3 *polygon,int numVertices)
{
D3DXVECTOR3 *Lines=new D3DXVECTOR3 [numVerts];
float total_angles,result,vlength;
int a;
for (a=0;a< numVertices;a++) // цикл всех вершин в многоугольнике
{
Lines[a].x=plane_intersection.x-polygon[a].x; // получить вектор направления из пересечения с вершиной [a]
Lines[a].y=plane_intersection.y-polygon[a].y;
Lines[a].z=plane_intersection.z-polygon[a].z;
D3DXVec3Normalize((D3DXVECTOR3*)&Lines[a],(D3DXVECTOR3*)&Lines[a]);
}
total_angles=0.0; //начинаем считать эти углы
for (int b=0;b < numVertices-1;b++)
{
total_angles= total_angles+acos(D3DXVec3Dot(&Lines[b],&Lines[b+1])); // просчитываем углы между каждым вектором
}
total_angles= total_angles+acos(D3DXVec3Dot(&Lines[numVertices-1],&Lines[0])); // Последний вектор обработан вне цикла. ( L2 , L0 )
delete Lines;
if (fabsf (total_angles-6.28)< 0.005) // пусть 6.28 радиана (360 градусов) дальше от total_angles и с допуском возле нуля попадем ...
{
//попали в многоугольник
return true;
}
return false; //не попали в многоугольник
}

Мы должны написать следующую функцию, которая проверяет каждый обьект в сцене и проверяет каждый его многоугольник на столкновение. Сейчас наш луч находится в мировых координатах, это значит, что мы должны преобразовать каждую вершину каждом обьекте, это нужно сделать в мировом пространстве. Это сделать легко (мы просто умножаем вершины обьектов на мировую матрицу). Это огромное количество преобразований и для нас неприемлимо. Что если мы для каждого обьекта трансформируем луч в Modal Space, умножая 2 конечные точки луча на каждый обьект, инвертированный мировой матрицей.

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

Структура будет выглядеть примерно так:

struct MGM_GetPickDesc{
WORD object;
WORD face;
BOOL HitAnything;
};

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

MGM_GetPickDesc MGM_GetPick(D3DXVECTOR3 CameraPosition,WORD mousex,WORD mousey,float fov)
{
Вызываем MGM_MakeRay чтобы определить наш луч из позиции камеры
D3DXVECTOR3 LineStart=CameraPosition;
D3DXVECTOR3 LineEnd=MGM_MakeRay(fov,1.3333,mousex,mousey);
MGM_GetPickDesc HitObject;
HitObject.HitAnything=false;
Устанавливаем переменную дистанции, чтобы определить ближайший многоугольник 1000.0;
float ShortestDistance=1000.0;
Цикл, проверяющий каждый обьект в сцене
for (int a=0;a< NumberOfObjects;a++)
{
Трансформируем луч в каждый обьект пространства, умножая начальные и конечные точки луча на обратную матрицу обьектов:-
D3DXMATRIX matWorld;
D3DXVECTOR3 TransformedLineStart,TransformedLineEnd;
D3DXMatrixInverse(&matWorld,&det,&object[a].matWorld);
D3DXVec3TransformCoord(&TransformedLineEnd,&LineEnd,&matWorld);
D3DXVec3TransformCoord(&TransformedLineStart,&LineStart,&matWorld);
...
...
Цикл, который проверяет каждую грань нашего обьекта
for (int b=0;b< Object[a].NumFaces;b++)
{
создаем нормаль грани, используя векторное произведение. Вызываем Call MGM_GetIntersect() используя начало и конец линии, мировую позицию первой вершины в грани и нормаль грани
float dist;
if (MGM_GetIntersect(LineStart,LineEnd,Object[a].Face[b].Vertex[0],Normal,& intersection,& dist)==true)
{
Если произошло пересечение с плоскостью и если возвращенная дистанция меньше, чем предыдущая записанная в MGM_HitPolygone, тогда эту дистанцию записываем как текущую
if (dist< ShortestDistance) {
ShortestDistance=dist;
Если попадание произошло, запоминаем обьект и грань. Функция MGM_GetPick обьединяет все вызовы функций в одно.
HitObject.Object=a;
HitObject.Face=b;
HitObject.HitAnything=true;
}
}
}
return HitObject;
}

 

PMG  5 марта 2001 (c)  Яценко Борис и Бутко Платон