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

406

Глава 13

и объекта столкновения, следует рассмотреть такие методы, как back-stepping time or time-stepping. Я рассмотрел time-stepping в главе 7, так что вы можете использовать те же самые технологии для моделирования одежды.

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

Создание класса меша одежды

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

Для класса меша одежды будут использованы три класса:

cClothPoint, который содержит информацию о каждой точке одежды, содержащейся в меше;

cClothSpring, который содержит информацию о пружинах, включая информацию о соединенных вершинах, длины покоя каждой пружины, значения жесткости и амортизации;

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

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

Единственный класс, который вы будете непосредственно использовать - это cClothMesh, который использует классы CClothPoint и cClothSpring для хранения данных точек и пружин одежды соответственно. Давайте посмотрим на объявления классов cClothPoint и cClothSpring.

// Класс, содержащий информации о точках одежды class cClothPoint

{

public:

D3DXVECTOR3 M_vecOriginalPos; // Исходное положение точки D3DXVECTOR3 m_vecPos; // Текущее положение точки

Имитирование одежды и анимация мешей мягких тел

D3DXVECTOR3 m_vecForce; // Сила, приложенная к точке D3DXVECTOR3 m_vecVelocity; // Скорость точки

float m_Mass; // Масса объекта (0=прикрепленная) float m_OneOverMass; // 1/Массу

};

// Класс, содержащий информацию о пружинах class cClothSpring

{

public:

DWORD m_Point1; // Первая точка пружины DWORD m_Point2; // Вторая точка пружины

float m_RestingLength; // Длина покоя пружины

float m_Ks; // Значение константы пружины float m_Kd; // Значение амортизации пружины

cClothSpring *m_Next; // Следующая пружина в связанном списке public:

cClothSpring() { m_Next = NULL; }

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

};

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

Класс cClothMesh объявлен так:

// Класс, полностью содержащий меш одежды (с встроенным анализатором .X) class cClothMesh : public cXParser

{

protected:

DWORD m_NumPoints; // # точек в одежде cClothPoint *m_Points; // точки

DWORD m_NumSprings; // # пружин в одежде cClothSpring *m_Springs; // Пружины

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

DWORD m_NumFaces; // # граней в меше

DWORD *m_Faces; // грани

DWORD m_VertexStride; // Размер вершины

408

Глава 13

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

protected:

// Анализировать .X файл в поисках данных масс и пружин BOOL ParseObject(IDirectXFileData *pDataObj,

IDirectXFileData *pParentDataObj, DWORD Depth,

void **Data, BOOL Reference);

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

public:

cClothMesh();

~cClothMesh();

// Создать одежду из предоставленного указателя меша

BOOL Create(ID3DXMesh *Mesh, char *PointSpringXFile = NULL);

// Освободить данные одежды void Free();

Используя функцию Create вы можете преобразовать исходный меш и дополнительно предоставляемый файл .X, содержащий данные масс точек и пружин, в данные одежды. Функция Free предназначена для освобождения ресурсов, выделенных для хранения данных одежды; она должны вызываться только при окончании работы с классом меша одежды.

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

//Установить силы, действующие на точки void SetForces(float LinearDamping,

D3DXVECTOR3 *vecGravity, D3DXVECTOR3 *vecWind, D3DXMATRIX *matTransform, BOOL TransformAllPoints);

//Обработать силы

void ProcessForces(float Elapsed);

// Обработать столкновения

void ProcessCollisions(cCollision *Collision, D3DXMATRIX *matTransform);

Имитирование одежды и анимация мешей мягких тел

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

// Воссоздать меш одежды

void RebuildMesh(ID3DXMesh *Mesh);

// Сбросить точки к их исходному положению, а также сбросить силы void Reset();

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

// Добавить пружину в список

void AddSpring(DWORD Point1, DWORD Point2, float Ks = 8.0f, float Kd = 0.5f);

// Установить массу точки

void SetMass(DWORD Point, float Mass);

Вцелях экономии памяти и исключения повторений пружин меша одежды функция AddSpring проверяет существующий список на наличие добавляемой пружины. Если найдено совпадение с добавляемой пружиной, то она будет проигнорирована. Функция SetMass принимает в качестве параметра индекс изменяемой точки и новое значение массы. Она также вычисляет новое значение 1/масса.

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

//Функции получения данных точек/пружин/граней

DWORD GetNumPoints(); cClothPoint *GetPoints();

DWORD GetNumSprings(); cClothSpring *GetSprings();

DWORD GetNumFaces();

DWORD *GetFaces();

};

410 Глава 13

После того как мы объявили класс cClothMesh, пришло время посмотреть на код каждой из его функций. Вы уже видели код ParseObject и ProcessCollisions, поэтому я не буду приводить его здесь. Начав сначала, я приведу код трех важных функций: Create, SetForces и ProcessForces. (Код остальных функций вы можете посмотреть на компакт-диске.)

Функция Create используется для создания меша одежды из указанного объекта ID3DXMesh.

BOOL cClothMesh::Create(ID3DXMesh *Mesh, char *PointSpringXFile)

{

DWORD i;

//Освободить предыдущий меш Free();

//Проверка ошибки

if(!Mesh) return FALSE;

// Вычислить шаг вершины (размер ее данных) m_VertexStride = D3DXGetFVFVertexSize(Mesh->GetFVF());

///////////////////////////////////////////////////////

//Вычислить информацию пружин из загруженного меша

//////////////////////////////////////////////////////

//Получить количество граней и создать массив

m_NumFaces = Mesh->GetNumFaces();

m_Faces = new DWORD[m_NumFaces*3];

// Заблокировать буфер индексов и скопировать данные (16-битные индексы)

unsigned short *Indices; Mesh->LockIndexBuffer(0, (void**)«Indices); for(i=0;i<m_NumFaces*3;i++)

m_Faces[i] = (DWORD)*Indices++; Mesh->UnlockIndexBuffer();

//Получить количество точек меша и создать структуры m_NumPoints = Mesh->GetNumVertices();

m_Points = new cClothPoint[m_NumPoints]();

//Заблокировать буфер вершин и поместить данные в точки одежды char *Vertices;

Mesh->LockVertexBuffer(0, (void**)&Vertices); for(i=0;i<m_NumPoints;i++) {

//Получить указатель на координаты вершин

sClothVertexPos *Vertex = (sClothVertexPos*)Vertices;

Имитирование одежды и анимация мешей мягких тел

//Сохранить положение, скорость, силу и массу m_Points[i].m_vecOriginalPos = Vertex->vecPos; m_Points[i].m_vecPos = m_Points[i].m_vecOriginalPos; m_Points[i].m_Mass = 1.0f; m_Points[i].m_OneOverMass = 1.0f;

//Установить состояния точек

m_Points[i].m_vecVelocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

m_Points[i].m_vecForce = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

// Перейти к следующей вершине

Vertices += m_VertexStride;

}

Mesh->UnlockVertexBuffer();

// Создать список пружин из вершин граней for(i=0;i<m_NumFaces;i++) {

//Получить вершины, образующие грань DWORD Vertex1 = m_Faces[i*3];

DWORD Vertex2 = m_Faces[i*3+1]; DWORD Vertex3 - m_Faces[i*3+2];

//Добавить пружины из 1->2, 2->3 и 3->1 AddSpring(Vertex1, Vertex2); AddSpring(Vertex2, Vertex3); AddSpring(Vertex3, Vertex1);

}

// Получить массы одежды и пружины из файла if(PointSpringXFile)

Parse(PointSpringXFile);

return TRUE;

}

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

Заблокировав буфер вершин, вы копируете координаты вершин в соответствующие точки одежды, при этом устанавливая значения их масс, скоростей, сил и 1/масса. После того как вы просмотрели все вершины и создали данные точек одежды, вы заканчиваете созданием пружин и анализом .X файла. (Вы создаете пружину, получая три индекса, образующие грань, и вызывая три раза функцию AddSpring, для соединения трех точек.)

412

Глава 13

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

void cClothMesh::SetForces(float LinearDamping, D3DXVECTOR3 *vecGravity,

D3DXVECTOR3 *vecWind, D3DXMATRIX *matTransform, BOOL TransformAllPoints)

{

DWORD i;

// Проверка ошибок

if(!m_NumPoints || m_Points == NULL) return;

Функция SetForces начинается с прототипа и нескольких строчек кода, выполняющих проверку для убеждения, что были заданы точки одежды. В качестве параметров функции SetForces передаются значения линейной амортизации (установленное в отрицательное значение -0.05f), векторы, представляющие направление

ивеличину сил гравитации и ветра, матрица преобразования, применяемая к точкам,

ифлаг, определяющий, какая точка преобразуется.

Вы уже читали о линейной амортизации и векторах сил (гравитации и ветра), но я еще ничего не упоминал о преобразовании координат точек. В чем была бы польза меша одежды, если бы он не мог перемещаться по трехмерному миру? Передав матрицу преобразования SetForces, вы можете преобразовать точки перед вычислением сил.

Вы спросите, а какие точки преобразовывать? Это зависит от флага TransformAllPoints. Если вы установите TransformAllPoints в TRUE, что вы и должны сделать самый первый раз для расположения и ориентации точек меша одежды

втрехмерном мире, тогда все точки используют матрицу преобразования. Установка TransformAllPoints в FALSE означает, что матрица преобразования влияет только на точки с нулевой массой (т. е. точки с ненулевой массой будут двигаться

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

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

//Очистить силы, применить преобразование, установить гравитацию и

//применить линейную амортизацию

for(i=0;i<m_NumPoints;i++) {

// Очистить cилы

m_Points[i].m_vecForce = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

Имитирование одежды и анимация мешей мягких тел

// Переместить точки, используя преобразование, если задано if(matTransform && (TransformAllPoints == TRUE || \ m_Points[i].m_Mass == 0.0f)) {

D3DXVec3TransformCoord(&m_Points[i].m_vecPos, \ &m_Points[i].m_vecOriginalPos, \ matTransform);

}

//Применять гравитацию и линейную амортизацию только к точкам

сненулевой

//массой

if(m_Points[i].m_Mass != 0.0f) {

// Применить гравитацию, если задана if(vecGravity != NULL) m_Points[i].m_vecForce += (*vecGravity) * \

m_Points[i].m_Mass;

// Применить линейную амортизацию

m_Points[i].m_vecForce += (m_Points[i].m_vecVelocity * \ LinearDamping);

}

}

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

// Применить ветер

if(vecWind != NULL && m_NumFaces) {

// Просмотреть все грани и применить ветер ко всем их вершинам for(i=0;i<m_NumFaces;i++) {

//Получить три вершины, образующие грань DWORD Vertex1 = m_Faces[i*3];

DWORD Vertex2 - m_Faces[i*3+1]; DWORD Vertex3 = m_Faces[i*3+2];

//Вычислить нормаль грани

D3DXVECTOR3 vecV12 = m_Points[Vertex2].m_vecPos - \ m_Points[Vertex1].m_vecPos;

D3DXVECTOR3 vecV13 = m_Points[Vertex3].m_vecPos - \ m_Points[Vertex1].m_vecPos;

D3DXVECTOR3 vecNormal; D3DXVec3Cross(&vecNormal, &vecV12, &vecV13); D3DXVec3Normalize(&vecNormal, &vecNormal);

414

//Получить скалярное произведение нормали и ветра float Dot = D3DXVec3Dot(&vecNormal, vecWind);

//Увеличить нормаль на величину скалярного произведения vecNormal *= Dot;

//Применить нормаль к вектору силы точки

m_Points[Vertex1].m_vecForce += vecNormal; m_Points[Vertex2].m_vecForce += vecNormal; m_Points[Vertex3].m_vecForce += vecNormal;

}

}

Последний, и самый важный, кусок кода применяет силы пружин.

// Обработать пружины cClothSpring *Spring = m_Springs; while(Spring) {

// Получить текущий вектор пружины D3DXVECTOR3 vecSpring;

vecSpring = m_Points[Spring->m_Point2].m_vecPos - \ m_Points[Spring->m_Point1].m_vecPos;

// Получить текущую длину пружины

float SpringLength = D3DXVec3Length(&vecSpring);

// Получить относительную скорость точек D3DXVECTOR3 vecVelocity;

vecVelocity = m_Points[Spring->m_Point2].m_vecVelocity - \ m_Points[Spring->m_Point1].m_vecVelocity;

float Velocity = D3DXVec3Dot(&vecVelocity, &vecSpring) / \ SpringLength;

// Вычислить скаляры силы

float SpringForce = Spring->m_Ks * (SpringLength - \ Spring->m_RestingLength);

float DampingForce = Spring->m_Kd * Velocity;

//Нормализовать пружину vecSpring /= SpringLength;

//Вычислить вектор силы

D3DXVECTOR3 vecForce = (SpringForce + DampingForce) * \ vecSpring;

// Применить силу к векторам if(m_Points[Spring->m_Point1].m_Mass != 0.0f) m_Points[Spring->m_Point1].m_vecForce += vecForce;

if(m_Points[Spring->m_Point2].m_Mass != 0.0f) m_Points[Spring->m_Point2].m_vecForce -= vecForce;

// Перейти к следующей пружине

Имитирование одежды и анимация мешей мягких тел

Spring = Spring->m_Next;

}

}

Опять же здесь нет ничего, чего бы вы не видели. После того как вы вызвали SetForces необходимо обработать силы и переместить точки одежды в соответствии с накопленными силами.

voidcClothMesh::ProcessForces(floatElapsed)

{

// Проверка ошибок if(!m_NumPoints || !m_Points) return;

// Рассчитать силы точек for(DWORD i=0;i<m_NumPoints;i++) {

// Точки с нулевой массой не движутся if(m_Points[i].m_Mass != 0.0f) {

// Обновить скорость m_Points[i].m_vecVelocity+= (Elapsed * \

m_Points[i].m_OneOverMass* \ m_Points[i].m_vecForce);

// Обновить положение m_Points[i].m_vecPos += (Elapsed *

m_Points[i].m_vecVelocity);

}

}

}

Коротко и ясно, функция ProcessForces делает то, что и должна - просматривает список точек одежды и для каждой точки линейно интегрирует скорость и положение на основе сил точки и величины прошедшего времени (которую вы задаете при вызове ProcessForces). Заметьте, что параметр Elapsed представляет собой количество интегрируемых секунд. Если вы хотите задать миллисекунды, необходимо задать дробное значение, как я уже замечал ранее.

Что вы думаете о том, чтобы показать вам, как использовать класс cClothMesh в ваших собственных проектах? Сначала убедитесь, что имеется корректный объект ID3DXMesh, после чего вызовете функцию Create для создания необходимых данных одежды.

Замечание. Вы не должны обрабатывать (интегрировать) более 10-20 миллисекунд за один раз. Если вы хотите обработать более 20 миллисекунд за один раз, вы должны сделать несколько вызовов SetForces и ProcessForces, каждый раз задавая небольшой интервал времени. Например, вы можете вызвать ProcessForces, указав в качестве параметра обараза 20 миллисекунд, чтобы в общем обработать 40 миллисекунд.

416

//pMesh = предварительно загруженный объект ID3DXMesh cClothMesh ClothMesh;

//Создать данные одежды, вызвав Create ClothMesh.Create(pMesh);

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

void FrameUpdate()

{

static DWORD LastTime = timeGetTime()-1; DWORD ThisTime = timeGetTime();

DWORD Elapsed;

//Вычислить прошедшее время Elapsed = ThisTime - LastTime; LastTime = ThisTime;

//Установить векторы гравитации и тяжести

D3DXVECTOR3 vecGravity = D3DXVECTOR3(0.0f, -9.8f, 0.0f); D3DXVECTOR3 vecWind = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

// Установить силы одежды ClothMesh.SetForces(-0.05f,&vecGravity,&vecWind,NULL,FALSE);

//Обработать силы одежды,основываясь на прошедшем времени. Убедитесь, //что время задано в миллисекундах,для чего разделите прошедшее время //на 1000

ClothMesh.ProcessForces((float)Elapsed / 1000.0f);

//Обработать столкновения, если они есть

//Воссоздать меш ClothMesh.RebuildMesh(pMesh);

//Визуализировать меш одежды любым удобным способом

}

Чтобы облегчить использование cClothMesh, вы можете запустить демонстрационную программу с компакт-диска. Она очень подробно откомментирована, и иллюстрирует возможности класса меша одежды. Наслаждайтесь!

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