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

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

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

Функция cRagdoll::GetBoundBoxSize вычисляет ограничивающий параллелепипед. В качестве ее параметров задаются указатель на структуру фрейма (который представляет собой кость) и два вектора, содержащие размер ограничивающего параллелепипеда и смещение его центра относительно точки присоединения к родительской кости.

void cRagdoll::GetBoundingBoxSize(D3DXFRAME_EX *pFrame, D3DXVECTOR3 *vecSize, D3DXVECTOR3 *vecJointOffset)

{

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

// Установить минимальные и максимальные координаты по умолчанию D3DXVECTOR3 vecMin = D3DXVECTOR3(0.0f, 0.0f, 0.0f); D3DXVECTOR3 vecMax = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

Первой задачей GetBoundingBoxSize является поиск кости, имеющей такое же имя, как и заданный фрейм. Эта кость, точнее интерфейс объекта кости скелетного меша (ID3DXSkinInfo), делает запрос о вершинах, присоединенных к ней.

// Обрабатывать вершины кости, если кость задана if(pFrame->Name) {

// Получить указатель на интерфейс ID3DXSkinInfo ID3DXSkinInfo *pSkin = pMesh->pSkinInfo;

// Найти кость с таким же именем, как и у фрейма DWORD BoneNum = -1;

for(DWORD i=0;i<pSkin->GetNumBones();i++) { if(!strcmp(pSkin->GetBoneName(i), pFrame->Name)) { BoneNum = i; break;

}

}

// Обработать вершины, если кость была найдена

if(BoneNum != -1) {

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

// Получить количество присоединенных вершин

DWORD NumVertices = pSkin->GetNumBoneInfluences(BoneNum);

if(NumVertices) {

// Получить влияния костей

DWORD *Vertices = new DWORD[NumVertices]; float *Weights = new float[NumVertices]; pSkin->GetBoneInfluence(BoneNum, Vertices, Weights);

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

// Получить тип данных вершин DWORD Stride = D3DXGetFVFVertexSize( \ pMesh->MeshData.pMesh->GetFVF());

// Получить обратную матрицу преобразования смещения кости D3DXMATRIX *matInvBone = \

pSkin->GetBoneOffsetMatrix(BoneNum);

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

pMesh->MeshData.pMesh->LockVertexBuffer( \

D3DLOCK_READONLY, (void**)&pVertices); for(i=0;i<NumVertices;i++) {

// Получить указатель на координаты вершин D3DXVECTOR3 *vecPtr = \

D3DXVECTOR3*)(pVertices+Vertices[i]*Stride);

// Преобразовать вершины на преобразование смещения кости

D3DXVECTOR3 vecPos;

D3DXVec3TransformCoord(&vecPos, vecPtr, matInvBone) ;

// Получить минимальные/максимальные значения vecMin.x = min(vecMin.x, vecPos.x) ; vecMin.y = min(vecMin.y, vecPos.y) ; vecMin.z = min(vecMin.z, vecPos.z);

vecMax.x = max(vecMax.x, vecPos.x) ; vecMax.y = max(vecMax.y, vecPos.y) ; vecMax.z = max(vecMax.z, vecPos.z);

}

pMesh->MeshData.pMesh->UnlockVertexBuffer();

// Освободить ресурсы delete [] Vertices; delete [] Weights;

}

} }

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

// Учесть точки присоединения потомков в размерах if(pFrame->pFrameFirstChild) {

// Получить обратное преобразование кости для расположения // дочерних соединений D3DXMATRIX matlnvFrame;

D3DXMatrixInverse(&matInvFrame,NULL,&pFrame->matCombined); // Перебрать все дочерние фреймы, присоединенные к текущему D3DXFRAME_EX *pFrameChild = \

D3DXFRAME_EX*)pFrame->pFrameFirstChild; while(pFrameChild) {

// Получить координаты вершины фрейма и преобразовать их

D3DXVECTOR3 vecPos;

vecPos = D3DXVECTOR3(pFrameChild->matCombined._41,

pFrameChild->matCombined._42,

pFrameChild->matCombined._43); D3DXVec3TransformCoord(&vecPos, SvecPos, SmatInvFrame);

// Получить минимальные/максимальные значения vecMin.x = min(vecMin.x, vecPos.x); vecMin.y = min(vecMin.y, vecPos.y); vecMin.z = min(vecMin.z, vecPos.z) ;

vecMax.x = max(vecMax.x, vecPos.x); vecMax.y = max(vecMax.y, vecPos.y); vecMax.z = max(vecMax.z, vecPos.z);

// Перейти к следующей дочерней кости

pFrameChild = (D3DXFRAME_EX*)pFrameChild->pFrameSibling;

}

}

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

Теперь вы можете закончить функцию, сохранив размер параллелепипеда. Если его размер слишком мал, то он устанавливается в минимальное значение (при помощи макроса MINIMUM_BONE_SIZE, который устанавливает в 1.0)

// Установить размер ограничивающего параллелепипеда vecSize->x = (float)fabs(vecMax.x - vecMin.x); vecSize->y = (float)fabs(vecMax.y - vecMin.y); vecSize->z = (float)fabs(vecMax.z - vecMin.z);

// Убедиться, что каждая кость имеет минимальный размер if(vecSize->x < MINIMUM_BONE_SIZE) {

vecSize->x = MINIMUM_BONE_SIZE;

vecMax.x = MINIMUM_BONE_SIZE*0.5f;

}

if(vecSize->y < MINIMUM_BONE_SIZE) { vecSize->y = MINIMUM_BONE_SIZE; vecMax.y = MINIMUM_BONE_SIZE*0.5f;

}

ifvecSize->z < MINIMUM_BONE_SIZE) { vecSize->z = MINIMUM_BONE_SIZE; vecMax.z = MINIMUM_BONE_SIZE*0.5f;

}

// Установать смещение кости в центр, основываясь на половине // размера ограничивающего параллелепипеда и максимальном / / положении

(*vecJointOffset) = ((*vecSize) * 0.5f) - vecMax;

}

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

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

Создание данных костей || Оглавление || Установка сил