Для создания класса кости твердого тела, представляющего каждую кость, сначала необходимо получить обратную матрицу преобразования кости, используя функцию скелетного меша 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. Т. к. кость твердого тела может быть любого размера, а вы отслеживаете ее с помощью координат ее центра, необходимо создать дополнительную точку, которая бы представляла собой точку присоединения ограничивающего параллелепипеда к родителю. Это и есть вектор смещения соединения. Вы узнаете о нем больше при изучении усиления связей костей.
После того как вы вычислили размер ограничивающего параллелепипеда и установили разнообразные данные кости, вы можете прикладывать различные силы для нахождения движения костей.