Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DirectX. Продвинутая Анимация (2004) [rus].pdf
Скачиваний:
335
Добавлен:
16.08.2013
Размер:
8.39 Mб
Скачать

212 Глава 7

Вектор из центра ограничивающего параллелепипеда в координаты кости является смещением соединения. Смещение от координат родительской кости (которые изменяются при помощи обратной матрицы преобразования кости) до координат кости дает вектор родительского смещения. Вы узнаете об этом более подробно чуть позже. Пока что вы научились накладывать силы на линейную скорость и угловой момент и заставлять кости воссоединяться. Что далее? Обработка столкновений, вот что!

Обеспечение обнаружения столкновений и ответной реакции

Т. к. твердые тела состоят из твердых объектов, они никогда не меняют форму и не пересекают пространство других объектов. Столкновение с другими объектами означает, что твердые тела соответствующим образом взаимодействуют, в результате чего они меняют движение и вращение. Это означает, что вам необходимо найти способ обнаружения столкновений твердых тел и соответствующим образом их обрабатывать. Давайте продолжим, подробнее рассмотрев обнаружение столкновений.

Проверка на столкновения

Помните, я упоминал, что использование восьми точек для твердого тела полезно не только для экономии памяти? Если вы еще не догадались, эти точки помогают обнаруживать столкновения! Т. к. ваши твердые тела состоят из восьми точек, вам просто необходимо проверить, попадают ли они в другой объект. Если хотя бы одна из точек попадает в другой объект, вы можете скорректировать движение твердого тела в соответствии с отклонением от сталкиваемого объекта.

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

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

// vecPos = D3DXVECTOR3 содержащий координаты точки

//

Plane = D3DXPLANE содержащий данные плоскости (нормаль

и

расстояние)

 

I

// Создать вектор, используя данные плоскости

D3DXVECTOR3 vecPlane = D3DXVECTOR3(Plane.a, Plane.b, Plane.c);

Созданиекукольнойанимации

// Выполнить скалярное умножения для получения расстояния от точки до плоскости

float Dist = D3DXVec3Dot(&vecPos, &vecPlane) + Plane.d;

Т. к. параметр d структуры D3DXPLANE представляет собой расстояние от плоскости до. начала координат, вы можете определить расстояние от точки до плоскости, прибавив это расстояние к скалярному произведению координат точки и нормали плоскости, как я вам показал. Если значение расстояния меньше или равно 0, то точка лежит на плоскости.

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

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

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

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

Позвольте привести пример. Положение точки хранится в vecPos, а координаты сферы в vecSphere. Радиус сферы хранится в Radius. Чтобы проверить, находится ли точка в пределах радиуса сферы, вы можете использовать следующий код:

214

//vecPos = положение проверяемой точки

//vecSphere = координаты, центра сферы

//Radius = вещественное число, содержащее радиус сферы

//Получить вектор из точки в центр сферы

D3DXVECTOR3 vecDiff = vecSphere - vecPos;

// Получить квадрат расстояния от точки до сферы float Dist = vecDiff.x * vecDiff.x +

vecDiff.y * vecDiff.y + vecDiff.z * vecDiff.z;

// Проверить является ли distance <= квадрата Radius if(Dist < (Radius * Radius)) {

// Точка лежит в сфере!

}

Как показывает код, вы создаете вектор из центра сферы в подозрительную точку. Квадрат длины этого вектора сравнивается с квадратом радиуса сферы. Если длина меньше или равна радиусу, тогда точка лежит в сфере.

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

Для плоскости, ее нормаль хранится в компонентах a, b и с структуры плоскости. Т. к. эти компоненты уже нормализованы, вам необходимо просто масштабировать вектор на значение скалярного произведения, вычисленного при определении пересечения точкой плоскости.

//Plane = структрура плоскости, с которой идет работа

//Dot = скалярное произведение вектора точки и нормали плоскости

//vecPosition = положение точки

D3DXVECTOR vecNormal = D3DXVECTOR3(Plane.a,

Plane.b,

Plane.c);

//Масштабировать нормаль на скалярное произведение vecNormal *= Dot;

//Добавить масштабированный вектор к положению точки vecPosition += vecNormal;

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

Созданиекукольнойанимации

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

//vecDiff = вектор из точки в сферу

//Radius = радиус сферы

//Dist = квадрат расстояния от точки до центра сферы

//Нормализовать вектор

D3DXVec3Normalize(&vecDiff, &vecDiff);

// Получить настоящее значение расстояния, вычислив

//квадратный корень переменной Dist Dist = (float)sqrt(Dist);

//Вычесть новое значение Dist из Radius, чтобы получить

//расстояние, на которое необходимо вытолкнуть точку из сферы float ToPush = Radius - Dist;

//Масштабировать нормализованный вектор, используя ToPush vecDiff *= ToPush;

//Добавить масштабированный вектор к положению точки vecPosition += vecDiff;

Теперь, когда вы знаете, как выполнять проверку расстояний по отношению к плоскостям и сферам и можете использовать ее результаты для обнаружения столкновений объектов (а если происходит проникновение, то и выталкивания объекта), что же осталось? Ну, вам необходимо сделать так, чтобы точка отскочила от поверхности объекта.

Ответная реакция на столкновения

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

Также как и другие силы (тяжести, пружины) влияют на движение твердых тел, влияет и реакция на столкновения. Как и пружинная сила, реакция на столкновение мгновенно изменяет положение и вращение твердого тела.

Так и есть, Люк; пока мы проигнорируем силу, вместо этого работая с импульсом! Совсем как темная сторона силы (я надеюсь, я не зря трачу метафоры на не фанатов "Звездных войн"), импульс является разновидностью силы. Вместо того чтобы долго и нудно прикладывать силы к объекту и заставлять его двигаться в соответствии с реакцией на столкновение, импульс немедленно перемещает объект от точки столкновения.

216

Глава 7

 

Вычисление импульса совершенно такое же, как и вычисление прилагаемой

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

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

Коэффициент возврата это просто скаляр, который определяет, насколько отскочит тело при столкновении с другим объектом (фактически, сколько силы теряется при столкновении).

Чем меньшее значение коэффициента используется, тем меньше тело отскакивает от объекта. Чем больше значение, тем сильнее. На самом деле, тела могут даже получать энегрию при столкновениях, если коэффициент больше 1. Коэффициент возврата хранится в вещественной переменной, которую я назвал Coefficient.

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

//vecPosition = координаты твердого тела

//vecPointPos = координаты точки столкновения

//vecCollisionNormal = нормаль сталкиваемого объекта

//Получить вектор из центра тела в точку столкновения D3DXVECTOR3 vecPtoP = vecPosition - vecPointPos;

//Получить векторное произведение vecPtoP и угловой скорости D3DXVECTOR3 vecCross;

D3DXVec3Cross(&vecCross, &vecAngularVelocity, &vecPtoP);

//Сложить эти два вектора для получения скорости точки D3DXVECTOR3 vecPointVelocity = vecLinearVelocity + vecCross;

Созданиекукольнойанимации

Теперь у вас есть вектор скорости точки столкновения, хранящийся в vecPointVelocity. Этот вектор используется для вычисления импульса, который действует непосредственно на положение и угловой момент твердого тела. Для вычисления импульса необходимо сначала посчитать числитель и знаменатель импульса для масштабирования нормали сталкиваемого объекта. Чтобы следующий далее код был читаемым, я буду использовать две функции DotProduct и CrossProduct, которые напрямую работают с кодом вычисления скалярного и векторного произведения двух векторов соответственно.

// Вычислить числитель импульса

real ImpulseNumerator = DotProduct(vecPointVelocity, \ vecCollisionNormal) * \

-(1.0f + Coefficient);

real ImpulseDenominator = (1.0f / Mass) + \ DotProduct(CrossProduct(matInvWorldInertiaTensor * \ CrossProduct(vecPtoP, vecCollisionNormal), \ vecPtoP), CollisionNormal);

Как вы и сами могли догадаться, вычисления ImpulseNumerator и ImpulseDenominator очень сложны. Хотя мне и хотелось бы посвятить время и место их полному объяснению, я просто не могу сделать этого. Целые статьи были написаны об их вычислении, так что я оставляю их им. Для примера, одну из таких статей, написанную Chris Hecker для журнала Game Developer, вы можете скачать на его сайте http://www.d6.com/users/checker.

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

D3DXVECTOR3 vecImpulse = vecCollisionNormal * \ (ImpulseNumerator/ImpulseDenominator);

Теперь у вас есть вектор, который представляет собой противодействующую силу, прикладываемую к сталкиваемому телу. Вы можете добавить этот вектор скорости vecImpulse непосредственно к линейной скорости и угловому моменту твердого тела.

// Добавьте силы, для окончания

vecLinearVelocity += vecImpulse;

// Вычислить векторное произведения для создания вектора момента D3DXVECTOR3 vecCross;

D3DXVec3Cross(&vecCross, &vecPtoP, &vecImpulse); vecAngularMomentum += vecCross;

218

Глава 7

Однако существует одна проблема, о которой я не упомянул. Как же быть с теми точками, которые сталкиваются с одним и тем же объектом одновременно? Если вы одновременно обработаете эти точки и скорректируете их положение и вращение в соответствии со столкновениями, вы обнаружите, что тела, параллельные сталкиваемому объекту, отскочат очень ненатурально.

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

Сохранение данных объекта столкновения

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

Первый объект, который содержит информацию об одном объекте столкновений, называется cCollisionObject.

class cCollisionObject { public:

DWORD m_Type; // Тип объекта

D3DXVECTOR3 m_vecPos; // Координаты сферы float m_Radius; // Радиус сферы

D3DXPLANE m_Plane; // Значения плоскости

cCollisionObject *m_Next; // Следующий объект в связанном списке

public:

cCollisionObject() { m_Next = NULL; } ~cCollisionObject() { delete m_Next; m_Next = NULL; }

};

Вы прочитаете более подробно об объекте столкновений в главе 13 "Имитирование одежды и анимация мешей мягких тел". Почему я так тяну с их объяснением, спросите вы? Это все благодаря современному чуду редактирования копий (и моему постоянно меняющемуся мнению) я собирал информацию. Однако я не позволю этому факту остановить вас от просмотра кода, так что давайте быстро пройдемся по классу.

Внутри класса cCollisionObject располагается объект D3DXPLANE, который используется для определения плоскости, в то время как вектор и вещественное значение используются для определения расположения и радиуса сферы. Фактически,

Созданиекукольнойанимации

класс cCollisionObject может содержать информацию как о плоскости, так и о сфере. По этой причине в класс введена переменная m_Туре, которая определяет тип объекта, содержащегося в классе, и принимает значение COLLISION_SPHERE для сферы или COLLISION_PLANE для плоскости.

Указатель m_Next указывает на следующий объект столкновения в загруженном списке объектов столкновения. Так и есть, объекты столкновения хранятся в связанном списке. По этой причине я создал конструктор и деструктор, которые ответственны за инициализацию и освобождение указателя связанного списка.

Говоря о более чем одном объекте столкновении, я хочу представить вам второй класс - cCollision.

class cCollision { public:

DWORD m_NumObjects; // # объектов cCollisionObject *m_Objects; // список объектов

public:

cCollision();

~cCollision();

void Free();

void AddSphere(D3DXVECTOR3 *vecPos, float Radius); void AddPlane(D3DXPLANE *PlaneParam);

};

Класс cCollision содержит связанный список объектов столкновений. Вы можете добавлять объекты, используя функции AddSphere и AddPlane, или очистить связанный список, вызвав Free. Параметры функций AddSphere и AddPlane очевидны: вы задаете центр и радиус добавляемой сферы или параметры добавляемой в связанный список плоскости.

Вы можете сразу перейти к главе 13 и более детально рассмотреть информацию об объектах столкновений или можете посмотреть исходный код, содержащийся на компакт-диске книги (расположенный в файлах Collision.cpp и Collision.h директории главы 7).

А пока что, позвольте показать вам, как создавать объекты плоскости и сферы, используемые во время моделирования.

//Экземпляр набора объектов столкновений cCollision Collision;

//Добавить основную плоскость в центр мира Collision.AddPlane(&D3DXPLANE(0.0f, 1.0f, 0.0f, 0.0f));

//Добавить сферу в (0,10,40) с радиусом 20 Collision.AddSphere(&D3DXVECTOR3(0.0f, 10.0f, 40.0f), 20.0f);

Соседние файлы в предмете Программирование на C++