Для создания класса кости твердого тела, представляющего каждую кость, сначала необходимо получить обратную матрицу преобразования кости, используя функцию скелетного меша 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->GetFvFO);
// Получить обратную матрицу преобразования смещения кости 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) будут храниться размеры ограничивающего параллелепипеда. Массив индексов вершин освобождается (как и весов вершин), и обработка продолжается нахождением точек присоединения кости к родителю и потомкам.
// Учесть точки присоединения потомков в размерах 1г(рРгате->рРгатеР1гзг-Сп11б!) {
// Получить обратное преобразование кости для расположения // дочерних соединений Р3РХМАТР1Х тал1пургате;
Р3РХМалг1х1пуегзе(&тал1пуРгате,ЫиРР,&рРгате->талСотЬ1пеб); // Перебрать все дочерние фреймы, присоединенные к текущему Р3РХРРАМЕ_ЕХ *рРгатеСЫ1б = \
Р3РХРРАМЕ_ЕХ*)рЕгате->рЕгатеЕ1гвРСЫ1б; whi1e(pРrameChi1б) {
// Получить координаты вершины фрейма и преобразовать их
Р3РХУЕСТОР3 уесРоБ,-
уесРоБ = D3DXVECTOR3(pРrameChi1б->matCombineб._41,
pРrameChi1б->matCombineб._42,
рРгатеСР^1б->та^огЛ^пеб._43) ; D3DXvec3TransformCoorб(&vecPos, &уесРоБ, &matInvРrame);
// Получить минимальные/максимальные значения уесМ^.х = т^п(уесМ^.х, уесРоБ.х) ; уесМ^.у = т^п(уесМ^.у, уесРов.у); уесМ^^ = пип(уесМ^^, vecPos.z);
уесМах.х = тах(уесМах.х, уесРоБ.х); уесМах.у = тах(уесМах.у, уесРов.у); уесМах^ = тах(уесМах^, vecPos.z);
// Перейти к следующей дочерней кости
pРrameChi1б = (D3DXРRAME_EX*)pРrameChi1б->pРrameSib1ing,•
}
}
Обычно для учета точек соединения берутся координаты присоединенной кости в мировом пространстве и преобразуются на обратную матрицу кости. После чего полученные координаты сравниваются с координатами, хранящимися в векторах уесМт и уесМах.
Теперь вы можете закончить функцию, сохранив размер параллелепипеда. Если его размер слишком мал, то он устанавливается в минимальное значение (при помощи макроса МШ1МиМ_ВОКЕ_817Е, который устанавливает в 1.0)
// Установить размер ограничивающего параллелепипеда vecSize->x = (f1oat)fabs(vecMax.x - уесМ^.х); vecSize->y = (f1oat)fabs(vecMax.y - уесМ^.у); vecSize->z = (f1oat)fabs(vecMax.z - уесМ^^);
// Убедиться, что каждая кость имеет минимальный размер if(vecSize->x < MINIMUM_BONE_SIZE) {
vecSize->x = MINIMUM_BONE_SIZE,•
уесМах.х = 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. Т. к. кость твердого тела может быть любого размера, а вы отслеживаете ее с помощью координат ее центра, необходимо создать дополнительную точку, которая бы представляла собой точку присоединения ограничивающего параллелепипеда к родителю. Это и есть вектор смещения соединения. Вы узнаете о нем больше при изучении усиления связей костей.
После того как вы вычислили размер ограничивающего параллелепипеда и установили разнообразные данные кости, вы можете прикладывать различные силы для нахождения движения костей.