Delphi - сбориник статей

         

Принцип костной деформации


Тот, кто работает в программе 3dStudioMax, отлично знает, что это такое. Для начала создаётся сетка - множество точек в пространстве и треугольники, вершинами которых являются эти точки. Затем создаются кости. Каждая кость имеет начальную точку и конечную точку. Затем, выражаясь языком MAX, применив модификатор Skin, мы привязываем к каждой кости определённое множество вершин сетки, и, после этого, при движении скелетных костей вместе с ними двигаются и привязанные к ним точки, поворачиваясь вокруг точек начал костей.

Теперь скажу о том, как принцип костной деформации будет реализован в моём алгоритме. В пространстве будут определены точки. Затем эти точки будут соединены костями. Для каждой кости будут определены начало, конец и привязанные вершины. Итак, следуют следующие определения: //Определение костей SkPoint=record //Точка имеет три координаты в пространстве x, y, z:double; end; PSkPointArray=^TSkPointArray; TSkPointArray=array[word] of SkPoint; PVertArray=^TVertArray; //Список привязанных вершин определим как множество //индексов этих точек TVertArray=array[word] of word; SkBone=record //Начало и конец кости - индексы точек скелета StartPoint:word; EndPoint:word; //Количество вершин сетки, привязанных к кости numVertices:word; //Массив индексов этих вершин VertArray:PVertArray; end; PSkBoneArray=^TSkBoneArray; TSkBoneArray=array[word] of SkBone; //Определение сетки TVertex=record x, y, z:single; end; PVertexArray=^TVertexArray; TVertexArray=array[word] of TVertex; TFace=array[0..2] of word; PFaceArray=^TFaceArray; TFaceArray=array[word] of TFace; //Эта запись определяет угол поворота кости и //привязанных точек в соответствующей плоскости TBoneState=record AngleYOZ, AngleXOZ, AngleXOY:single; end; PBoneStateArray=^TBoneStateArray; TBoneStateArray=array[word] of TBoneState;

Теперь определим основную структуру для сетки со скелетом. Она должна содержать массив вершин, массив полигонов, массив вершин текстуры, массив полигонов текстуры, массив точек скелета, массив костей, а так же временный массив для хранения координат вершин после деформации. SkinnedMesh=record //Количество вершин сетки VertexCount:word; //Количество полигонов FacesCount:word; //Количество вершин на текстурной карте TexVertexCount:word; //Массив вершин сетки Vertices:PVertexArray; //Массив треугольников сетки Faces:PFaceArray; //Массив вершин на текстурной карте TexVertices:PVertexArray; //Массив треугольников на текстурной карте TexFaces:PFaceArray; //Количество точек скелета PointCount:word; //Массив точек скелета Points:PSkPointArray; BoneCount:word; //Количество костей Bones:PSkBoneArray; //Массив костей Empty:single; //Не используется //Массив для хранения DeformationBoneState:PBoneStateArray; //углов поворота костей DeformatedVertices:PVertexArray; //Массив для хранения //координат вершин деформированной сетки end;

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

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

Допустим, что имеются кости 0, 1, 2, 3, 4. Из них кости 0, 1, 3 принадлежит части тела Upper_body, оставшиеся - Lower_body. Тогда массив индексов костей в ключевом движении Upper_body будет содержать (0, 1, 3), а Lower_body - (2,4). Итак, далее следует новые определения. PWordArray=^TWordArray; //Ключевое положение части тела BodyPartKeyFrame=record BoneCount:word; //Массив положений костей в данный ключевой кадр BoneState:PBoneStateArray; //Массив индексов костей BoneIndexes:PWordArray; //Момент времени, которому соответствует //данный ключевой кадр KFTimer:integer; end; PBodyPartAction=^TBodyPartAction; //Действие, выполняемое одной из частей тела TBodyPartAction=record //Продолжительность этого действия Duration:integer; //Количество ключевых кадров в действии KeyFrameCount:word; //Множество ключевых кадров KeyFrames:array[byte] of BodyPartKeyFrame; end; //Основной класс модели TModel=class Body:SkinnedMesh; //Сетка и скелет модели //Данные для анимации модели BodyParts:array[byte] of array[byte] of PBodypartAction; //Количество частей тела numBodyParts:byte; //Массив, содержащий количество движений //каждой части тела numActions:array[byte] of byte; //Имя модели ModelName:ShortString; //Текущие действия, выполняемые разными частями тела CurrentActions:array[byte] of byte; //Момент действия каждой части тела Timers:array[byte] of word; //Процедура осуществляет загрузку модели из файла Procedure LoadFromFile(filename:string); //Функция возвращает время, оставшееся до того, //как одна из частей тела //закончит выполнять текущее действие Function TimeLeft:word; //Записать углы поворота каждой кости в массив //DeformationBoneState из Body Procedure PresetRotateAngles(DeltaTimer:word); //Нарисовать модель Procedure DrawModel(x1, y1, x2, y2:single); overload; //Нарисовать модель, используя маску текстуры Procedure DrawModel(x1, y1, x2, y2, mx1, my1, mx2, my2:single); overload; //Процедура осуществляет стирание из памяти //всех данных и удаление класса Destructor Destroy; override; end;

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



Содержание раздела