Logo    
Деловая газета CitCity.ru CITKIT.ru - все об Open Source Форумы Все публикации Учебный центр Курилка
CitForum    CITForum на CD    Подписка на новости портала Море(!) аналитической информации! :: CITFORUM.RU
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

27.05.2017

Google
WWW CITForum.ru
С Новым годом!
2004 г.

Вариант реализации простейших костных деформаций с помощью Delphi

Ушаков Юрий, «Королевство Delphi»

Эксперимент

Мой эксперимент заключается в следующем. Создать 3d-движок с нуля. Это значит, что нужно, ни на что не опираясь, ввести формат тел, формат анимации, комплекс процедур для реализации в движке методов аналитической геометрии и физики. И, двигаясь по порядку, я начинаю с создания формата моделей

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

Тот, кто работает в программе 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;

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

Формат файла модели

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

Поскольку в OpenGL основной используемый формат данных - это GLFloat, который определён, как Single, то файл модели должен быть именно file of Single. Поскольку мы собираемся хранить в файле так же данные типа word, то можно заранее ввести ещё один новый тип, который послужит для записи этих данных в файл типа Single. Определить этот тип можно следующим образом:

b4=array[1..2] of word;

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

  • Ключ файла - 4-х байтная строка 'YEM' (в Delphi, как известно, размер строки равен длине строки плюс один байт)
  • Имя модели. Используется следующий принцип. Строка разбивается на отрезки длиной по 3 символа, а затем каждый отрезок записывается в файл как строка. При этом, в начале, сохраняется количество таких отрезков.
  • В следующие 6 байт будут записаны количество вершин модели, количество треугольников и количество вершин на текстурной карте. После этого 2 байта остаются пустыми.
  • Далее записаны координаты вершин сетки по 12 байт на каждую вершину.
  • Треугольники записываются следующим образом. 3 числа по 2 байта означают индексы вершин, ещё один байт оставлен пустым.
  • Вершины на текстурной карте записываются таким же образом, как и вершины сетки (вместо X,Y,Z будут U, V, W).
  • Треугольники на текстурной карте. Их, очевидно, столько же, сколько треугольников у всего тела.
  • В следующие 4 байта записаны 2 2-байтные числа - это количество точек и количество костей.
  • Далее записаны в таком же формате, как и вершины сетки, записаны координаты всех точек скелета.
  • После этого записываются кости. Заголовок каждой кости занимает 4 байта. 2 байта - индекс начальной точки, 2 - индекс конечной точки. После этого ещё 2 байта занимает количество прикреплённых к кости вершин, 2 байта - это индекс части тела, к которой относится данная кость, а после этого перечисляются индексы всех прикреплённых вершин по 2 байта на каждый, а между ними пустые 2-байтные промежутки.
  • Количество частей тела.
  • Названия частей тела, записанные в таком же формате, как имя модели.
  • Данные анимации для каждой части тела. Анимация одной части тела содержит:
    • Количество движений, которые выполняет часть тела (2 байта. +2 байта пустые)
    • Движения данной части тела. Движение включает:
      • Количество ключевых кадров (2 байта. +2 байта пустые)
      • Продолжительность действия - целое число в мс (2 байта. +2 байта пустые)
      • Ключевые кадры каждой части тела. Ключевой кадр включает:
        • Количество передвигаемых костей (2 байта. +2 байта пустые).
        • Индексы этих костей (по 4 байта, из них по 2 байта пустые)
        • Углы поворота этих костей (по 12 байт на каждую кость. По 4 байта на угол в одной плоскости).
        • Момент времени считая от начала движения, к которому относится ключевой кадр (2 байта. +2 байта пустые).

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

Procedure TModel.LoadFromFile(filename:string);
//4-х байтный тип, преобразуемый в Single
type b4=array[1..2] of word;

//Получить два целых числа - старшую и младшую пару 
//из двойного слова Single
Function Getb4ofd(n:single):b4;
var
p1:pointer;
p2:^b4;
begin
  p1:=@n;
  p2:=p1;
  Getb4ofd:=p2^;
end;

//Получить строку из 3-х символов из
Function GetStrOfD(n:single):string;
type
s3=string[3];
var
p1:pointer;
p2:^s3;
begin
  p1:=@n;
  p2:=p1;
  GetStrOfD:=p2^;
end;

var
//Переменные для доступа к файлу модели

  Function ReadString:ansistring;
  var
  x:single;
  l, k:integer;
  res:string;
    //Получить целое двойное слово из числа Single
    Function GetLIOfS(n:Single):LongInt;
    var
    p1:pointer;
    p2:^longint;
    begin
      p1:=@n;
      p2:=p1;
      GetLIOfs:=p2^;
    end;
  begin
    res:='';
    read(t, x);
    l:=GetLIOfS(x);
    for k := 1 to l do begin
      read(t, x);
      res:=res+GetStrofD(x);
    end;
    ReadString:=res;
  end;

После этого можно прочитать модель из файла, пользуясь описанием её формата.

Begin
  //Открыть файл для чтения
  assignfile(t, filename);
  reset(T);
  //Прочитать заголовок
  read(t, n);
  //Если он не такой, какой нужен, следовательно, 
  //файл не формата модели
  if getStrOfD(n)<>'YEM' then
  begin
    closefile(t);
    exit;
  end;
  //Прочитать из файла имя модели
  ModelName:=readstring;
  //Прочитать из файла данные о сетке
  read(t, body.Empty);  //Пустое место
  read(t, n);
  //Получить количество вершин
  body.VertexCount:=Getb4ofd(n)[1];
  //количество треугольников
  body.FacesCount:=Getb4ofd(n)[2];
  read(t, n);
  //И количество вершин на карте текстуры
  body.TexVertexCount:=Getb4ofd(n)[1];
  //Выделить подо всё это память
  GetMem(body.Vertices, 
         body.VertexCount*SizeOf(TVertex));
  GetMem(body.Faces, body.FacesCount*SizeOf(TFace));
  //Необходимо выделить память и для переменных 
  //временного назначения
  GetMem(body.DeformatedVertices, 
         SizeOf(TVertex)*body.VertexCount);
  GetMem(body.TexVertices, 
         body.TexVertexCount*SizeOf(TVertex));
  GetMem(body.TexFaces, body.FacesCount*Sizeof(TFace));
  for i := 0 to body.VertexCount-1 do begin
    //Ввести координаты вершин сетки
    read(t, body.Vertices[i].x);
    read(t, body.Vertices[i].y);
    read(t, body.Vertices[i].z);
  end;
  for i := 0 to body.FacesCount-1 do begin
    //Ввести индексы вершин треугольников
    read(t, n);
    body.Faces[i][0]:=Getb4ofd(n)[1];
    body.Faces[i][1]:=Getb4ofd(n)[2];
    read(t, n);
    body.Faces[i][2]:=Getb4ofd(n)[1];
  end;
  for i := 0 to body.TexVertexCount-1 do begin
    //Ввести координаты текстуры
    read(t, body.TexVertices[i].x);
    read(t, body.TexVertices[i].y);
    read(t, body.TexVertices[i].z);
  end;
  for i := 0 to body.FacesCount-1 do begin
    //Ввести треугольники на текстурной карте
    read(t, n);
    body.TexFaces[i][0]:=Getb4ofd(n)[1];
    body.TexFaces[i][1]:=Getb4ofd(n)[2];
    read(t, n);
    body.TexFaces[i][2]:=Getb4ofd(n)[1];
  end;
  read(t, n);
  //Получить количество точек
  body.PointCount:=Getb4ofd(n)[1];
  //и количество костей
  body.BoneCount:=Getb4ofd(n)[2];
  //Выделить для их хранения память
  GetMem(body.Points, body.PointCount*sizeof(SkPoint));
  GetMem(body.Bones, body.BoneCount*SizeOf(SkBone));
  for i := 0 to body.PointCount-1 do begin
    //Прочитать координаты точек
    read(t, body.Points[i].x);
    read(t, body.Points[i].y);
    read(t, body.Points[i].z);
  end;
  for i := 0 to body.BoneCount-1 do begin
    //Прочитать индексы точек начала и конца кости
    read(t, n);
    body.Bones[i].StartPoint:=Getb4ofd(n)[1];
    body.Bones[i].EndPoint:=Getb4ofd(n)[2];
    read(t, n);
    //Получить количество привязанных вершин
    body.Bones[i].numVertices:=Getb4ofd(n)[1];
    GetMem(body.Bones[i].VertArray, 
           2*body.Bones[i].numVertices);
    for j := 0 to body.Bones[i].numVertices-1 do begin
    //Получить индексы вершин сетки,привязанных к кости
      read(t, n);
      body.Bones[i].VertArray[j]:=Getb4ofd(n)[1];
    end;
  end;
  read(t, n);
  //Получить количество частей тела
  numBodyParts:=Getb4ofd(n)[1];
  //Прочитать названия частей тела
  for i := 0 to numBodyParts-1 do readString;
  //Выделить память для временного хранения 
  //углов поворота костей в данный момент времени
  GetMem(body.DeformationBoneState, 
         body.BoneCount*SizeOf(TBoneState));
  //Обнулить эти ячейки памяти
  for i := 0 to body.BoneCount-1 do begin
    body.DeformationBoneState[i].AngleYOZ :=0;
    body.DeformationBoneState[i].AngleXOZ :=0;
    body.DeformationBoneState[i].AngleXOY :=0;
  end;
  //Ввести данные анимации для каждой части тела
  for i := 0 to numBodyParts-1 do begin
    read(t, n);
    //Ввести количество движений, которые может совершать 
    //данная часть тела
    numActions[i]:=Getb4ofd(n)[1];
    for j := 0 to numactions[i]-1 do begin
      //Выделить память для хранения движения
      GetMem(BodyParts[i][j], SizeOf(TBodyPartAction));
      read(t, n);
      //Ввести из файла продолжительность движения и 
      //количество ключевых кадров
      BodyParts[i][j].Duration:=Getb4ofd(n)[1];
      BodyParts[i][j].KeyFrameCount:=Getb4ofd(n)[2];
      //Ввод ключевого кадра
      for m := 0 to BodyParts[i][j].KeyFrameCount-1 
                 do begin
        read(t, n);
        //Ввести количество задействованных в ключевом 
        //кадре костей
        BodyParts[i][j].KeyFrames[m].KFTimer:=
        Getb4ofd(n)[1];
        //и момент времени, в который выполняется 
        //этот ключевой кадр
        BodyParts[i][j].KeyFrames[m].BoneCount:=
        Getb4ofd(n)[2];
        //Выделить память для хранения ключевого кадра
        GetMem(BodyParts[i][j].KeyFrames[m].BoneState, 
		BodyParts[i][j].KeyFrames[m]
                 .BoneCount*SizeOf(TBoneState));
        GetMem(BodyParts[i][j].KeyFrames[m].BoneIndexes, 
		BodyParts[i][j].KeyFrames[m].BoneCount*2);
        for v := 0 to Bodyparts[i][j].KeyFrames[m]
                                     .BoneCount-1 
                   do begin
          //Получить данные о повороте костей 
          //в ключевом кадре
          read(t, n);
          BodyParts[i][j].KeyFrames[m].BoneState[v]
                                      .AngleYOZ:=n;
          read(t, n);
          BodyParts[i][j].KeyFrames[m].BoneState[v]
                                      .AngleXOZ:=n;
          read(t, n);
          BodyParts[i][j].KeyFrames[m].BoneState[v]
                                      .AngleXOY:=n;
          read(t, n);
          //и индексы этих костей
          Bodyparts[i][j].KeyFrames[m].BoneIndexes[v]:=
          GetB4ofd(n)[1];
        end;
      end;
    end;
  end;
  closefile(t);
end;

Теперь можно реализовать подпрограмму TimeLeft, однако, в силу её простоты, я надеюсь, читатель сможет сам в ней разобраться. Я же перейду к подпрограмме PresetRotateAngles, которая должна совершать следующие действия. Как мы знаем, в массиве Timers хранятся значения времени выполнения действия каждой частью тела. Программа должна определить, соответствуют ли эти моменты времени ключевым кадрам. Если да, то она просто переписывает углы поворота в массив DeformatedBonesState подобъекта Body, а если текущему моменту не соответствует ключевой кадр, программа должна определить два ближайших ключевых кадра и вычислить угол поворота кости. Напомню, что в условии нигде не говорилось о том, что ключевые кадры отсортированы в файле по возрастанию KFTimer. Поскольку потребуется создавать каким-то образом модели, то пользователь вряд ли сможет сразу узнать, в какие моменты времени потребуется вставить ключевые кадры. Поэтому при создании ключевые кадры не отсортированы. Итак, приступим. Введём три переменные для цикла и 9 переменных Single, которые будут хранить углы поворота в следующий момент времени, предыдущий и текущий. Кроме того, для поиска соседних ключевых кадров нам потребуются две переменные, которые как-то запомнят эти кадры, а, если ключевой кадр будет найден, его так же потребуется сохранить.

Procedure TModel.PresetRotateAngles(DeltaTimer:word);
var
i, j, k:integer;
Anglex1, anglex2, angley1, angley2, anglez1, 
anglez2, anglex, angley, anglez:single;
minupper, maxlower:integer;
KeyFrameIndex:integer;

Теперь нужно увеличить значение всех таймеров на DeltaTimer. А если при этом таймер переходит за предел времени, требуется вернуть его назад.

  for i := 0 to numBodyParts-1 do begin
    Timers[i]:=Timers[i]+DeltaTimer;
    if Timers[i]>BodyParts
                 [i, CurrentActions[i]].Duration then
      Timers[i]:=Timers[i]-BodyParts
                 [i, currentActions[i]].Duration;
  end;

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

Теперь значения углов поворота костей были записаны в массив DeformationBoneState подобъекта Body. Теперь можно составить алгоритм поворота этих костей. Модель должна содержать такую вершину скелета, положение которой не зависит ни от одной кости. Программа находит эту точку и пробует повернуть кости, которые начинаются в этой точке. По пути, она поворачивает и все кости, которые начинаются из конца текущей. Определим рекурсивную процедуру поворота костей.

Procedure TModel.Draw(x1, y1, x2, y2, mx1, 
                      my1, mx2, my2:single);
  Procedure RotateBone(BoneIndex:word);
  var
  R_i:integer;  //Чтобы не перепутать циключескую 
                //переменную из тела основной процедуры,
                //Добавим к этой префикс
  x, y, z:single;
  x0, y0, z0, d, alpha0:single;
  begin
    for R_i := 0 to Body.BoneCount-1 do
      //Если другая кость выходит из конца данной кости,
      //провести эту процедуру с ней
      if Body.Bones[R_i].StartPoint=Body.Bones
             [BoneIndex].EndPoint then RotateBone(R_i);
    //Получить координаты точки, вокруг 
    //которой совершить поворот
    x:=Body.Points[Body.Bones[BoneIndex].StartPoint].x;
    y:=Body.Points[Body.Bones[BoneIndex].StartPoint].y;
    z:=Body.Points[Body.Bones[BoneIndex].StartPoint].z;
    for R_i := 0 to Body.Bones[BoneIndex].numVertices-1 
                 do begin
      //Получить координаты поворачиваемой точки
      x0:=Body.DeformatedVertices
         [Body.Bones[BoneIndex].VertArray[R_i]].x;
      y0:=Body.DeformatedVertices
         [Body.Bones[BoneIndex].VertArray[R_i]].y;
      z0:=Body.DeformatedVertices
         [Body.Bones[BoneIndex].VertArray[R_i]].z;
      //Совершить поворот отдельно 
      //в трёх различных плоскостях
      d:=sqrt((y0-y)*(y0-y)+(z0-z)*(z0-z));
      alpha0:=arctan((z0-z)/(y0-y));
      if y0-y<0 then begin
        if alpha0<0 then alpha0:=alpha0+pi 
                    else alpha0:=alpha0-pi;
      end;
      alpha0:=alpha0+Body.DeformationBoneState
                    [BoneIndex].AngleYOZ;
      y0:=y+d*Cos(alpha0);
      z0:=z+d*sin(alpha0);
      d:=sqrt((x0-x)*(x0-x)+(z0-z)*(z0-z));
      alpha0:=arctan((z0-z)/(x0-x));
      if x0-x<0 then begin
        if alpha0<0 then alpha0:=alpha0+pi 
                    else alpha0:=alpha0-pi;
      end;
      alpha0:=alpha0+Body.DeformationBoneState
                    [BoneIndex].AngleXOZ;
      x0:=x+d*Cos(alpha0);
      z0:=z+d*sin(alpha0);
      d:=sqrt((x0-x)*(x0-x)+(y0-y)*(y0-y));
      alpha0:=arctan((y0-y)/(x0-x));
      if x0-x<0 then begin
        if alpha0<0 then alpha0:=alpha0+pi 
                    else alpha0:=alpha0-pi;
      end;
      alpha0:=alpha0+Body.DeformationBoneState
                    [BoneIndex].AngleXOY;
      x0:=x+d*Cos(alpha0);
      y0:=y+d*sin(alpha0);
      //Сохранить координаты вершин после поворота
      Body.DeformatedVertices[Body.Bones
                    [BoneIndex].VertArray[R_i]].x:=x0;
      Body.DeformatedVertices[Body.Bones
                    [BoneIndex].VertArray[R_i]].y:=y0;
      Body.DeformatedVertices[Body.Bones
                    [BoneIndex].VertArray[R_i]].z:=z0;
    end;
  end;

var
  PointsDerivation:array[word] of boolean;
  TmpVertex:TVertex;
  TmpFace:TFace;
  TmpTexVertex:TVertex;
  TmpTexFace:TFace;
  i, j, k:integer;

begin
  for i := 0 to Body.VertexCount-1 do
    //Записать значения вершин до деформации
    Body.DeformatedVertices[i]:=Body.Vertices[i];
  //Найти независимую точку
  for i := 0 to Body.PointCount-1 
             do PointsDerivation[i]:=false;
  for i := 0 to Body.PointCount-1 do
    for j := 0 to Body.BoneCount-1 do
      if Body.Bones[j].EndPoint = i 
         then PointsDerivation[i]:=true;
  for i := 0 to Body.BoneCount-1 do
    //Совершить поворот костей, 
    //начинающихся от независимой точки
    if not PointsDerivation[Body.Bones[i].StartPoint] 
       then RotateBone(i);

Теперь следует расхождение. Одна из подпрограмм рисует тело с маскировкой, другая - без неё. Честно скажу, не уверен в том, что процедура рисования сетки с маскировкой будет работать - была написана наспех. Однако первая процедура проверена. В силу её простоты я не буду её здесь приводить. В силу того, что рисование объектов с маскировкой не входит в тему настоящей статьи, считаю, что читатель сам сможет в ней разобраться, используя статьи соответствующей тематики. Так же считаю ненужным приводить здесь комментарии к процедуре Destroy. Ломать всегда легче, чем строить. Выгружать почти всегда легче, чем загружать. На этом могу сказать, что модуль для работы с моделью готов.

Однако сам модуль не умеет делать ровным счетом ничего. Сначала нужно составить, во-первых, программу для разработки таких моделей, а, во-вторых, какую-нибудь программу, которая бы использовала этот модуль. Скажу несколько слов только по поводу названной программы. Она составлена. И включена в пакет, включенный в статью. Эта программа загружает из текстового файла сетку и позволяет создать к ней скелет, а к скелету - анимацию. Сразу скажу, работать в ней - не в потолок плевать. Это Вам не MAX. Текстовые файлы должны иметь тип EYE, и следующий формат. В первой строке файла записаны количество вершин сетки, количество полигонов и количество вершин на карте текстуры. После этого в текстовом виде перечислены все необходимые данные. Заранее прошу прощения за то, что программа на английском языке. Дело в том, что, по крайней мере на моей машине, в двух операционных системах - Windows 98 и Windows XP, - разные кодировки русских букв, поэтому программа, составленная под WinXP, под Win98 выводит на экран иероглифы. Для создания файл EYE из 3DStudioMax можно воспользоваться поставляемым макросом. Идея макроса и экспорта сетки с текстурой заимствованы из статьи Экспорт текстурированных 3D персонажей.

Теперь скажу пару слов о тестовой программе. Она загружает файл модели и файл текстуры. Нельзя, чтобы модель была привязана к текстуре. Если модель не привязана к текстуре, то, пользуясь различными текстурами, можно, надевая их на одну и ту же модель, создавать разные объекты. Например, на сетку, которая могла бы быть лейтенантом, кроме текстуры лейтенанта можно надеть текстуры капитана, майора, и т.д. Загружаемая тестовой программой модель содержит слегка анимированного террориста из Counter Strike. Я говорю слегка потому, что он может выполнять совсем мало действий, однако этого достаточно, чтобы продемонстрировать возможности модуля. Надетая на него текстура является смесью текстуры персонажа и текстуры дробовика, позаимствованной мною из игры Medal of Honour. После этого при нажатии на форме появляется человек, вооружается и начинает бежать, наставив на кого-то прицел. Программа ведёт подсчёт FPS и выводит его в заголовке. На процессоре Pentium 1.7 GHz мне удалось выжать из программы скорость 110 FPS, однако такое случилось только один раз, когда я под WinXP выгрузил из памяти все ненужные службы. В обычном же режиме скорость составляла около 50, если на форму был наведён курсор мыши и 62, если курсор был убран с экрана. Можно сказать, что модель не обладает таким уж высоким быстродействием, как модели Quake MD3, однако это отражается и на объёме файла модели. Кроме того, программа имеет один серьёзный недостаток. При запуске таймера нажатием на форму FPS равна 0, поэтому иногда человек начинает с феноменальной скоростью махать ружьем и ногами, но это продолжается лишь секунду.

Дальше я скажу о том, как создать такого человека с помощью поставляемого комплекта. Во-первых, я буду использовать 3DStudioMax. Если у Вас нет этой программы, то можете использовать заранее экспортированный файл сетки ManArmed.eye. Запустим программу EditMdl.exe. Выберете в открывшемся окне фильтр файлы *.eye и откройте файл ManArmed.eye. В следующем окне введите рост Вашего персонажа. Я выбрал 1.8 (или 2? - забыл). Наконец, в следующем окне выберете файл текстуры g2.bmp. Затем, если в открывшемся окне редактора выбрать вид Перспективы, перед нами предстаёт грозного вида мужчина с опасной игрушкой. (См. рисунок)

Теперь необходимо создать вершины скелета. Для данной модели это удобнее всего делать в проекции Left. Замечу, что заполнение полей x, y, z производится следующим образом. Когда курсор стоит в окне x, то при щелчке мышью на окне проекции заполняются все поля. Если курсор стоит в окне y, то заполнятся только y и z. Если курсор установлен в окно z, то только это окно и заполняется. Однако, не забывайте, что в проекции Left создаваемой точке будет присваиваться координата x=0. Координаты указателя в пространстве можно видеть в верхней полосе дисплея. Далее необходимо создать кости. Для выбора точки скелета в качестве начала или конца создаваемой кости, достаточно выделить точку с помощью мыши. Аналогично выбираются вершины сетки. Помните, что при выделении с вершинами будет проделана та операция, которая выбрана в окне опций.

После того, как скелет создан, в проекции Left вы можете увидеть нечто похожее на это:

Самое время сохранить изменения. Обратите внимание, что программа сохраняет изменения в файл формата sks. Это связано с тем, что этот файл не является текстовым экспортированным файлом, и не является ещё файлом модели. Теперь нажмите кнопку Части Тела и распределите кости между двумя частями тела: UpperBody и LowerBody. Конечно, можно было создать и голову, но зачем? Ведь наш персонаж не имеет рта и не умеет разговаривать. Можно перейти в другой режим, нажав кнопку Animate. При этом необходимо ввести имя создаваемой модели. Это необходимо, если мы захотим, чтобы нашей моделью затем управлял составленный на языке VBScript сценарий. Нажав на ОК, Вы попадёте в режим анимации.

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

Редактор содержит так же множество недостатков. Один из них - это то, что окна редактора всегда прячутся под другие открытые окна. Если Вы, открыв редактор и выбрав в первом окне файл модели, не обнаружили заметной реакции программы, нужно с помощью Alt+Tab переключиться на значок Кусок Мяса И Кость. Другой недостаток - это то, что иногда окна прячутся под вид, отображаемый OpenGL.

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

Использованная литература.

  1. 3d Studio Max 3 для профессионалов
  2. Самоучитель Программирование в Turbo Pascal 7.0 и Delphi
  3. Статья Экспорт текстурированных 3D персонажей

Использованные материалы.

  1. Модель позаимствована из уже названной статьи.
  2. Текстура ружья Winchester позаимствована у ID Software (Medal of Honor Allied Assault)
Скачать исходные тексты и примеры (433К).

Размещение рекламы — тел. +7 495 4119920, ICQ 232284597

Подписка на новости IT-портала CITForum.ru
(библиотека, CITKIT.ru, CitCity)

Новые публикации:

24 декабря

CITKIT.ru:

  • Новогодние поздравления
  • Сергей Кузнецов. Цикл Операционные системы: Ностальгия по будущему:

  • Алексей Федорчук. OpenSolaris 2008.11 Release

  • Сергей Голубев:

  • Евгений Чайкин aka StraNNik (Блогометки):

    17 декабря

  • С.Д.Кузнецов. Базы данных. Вводный курс

    10 декабря

    CITKIT.ru:

  • OpenSolaris 2008.11 Release

  • Альтернативные ОС: две грустные истории (С.Кузнецов)
  • Nokia N810 — доведение до ума
  • CitCity:

  • Платформа 2009: заоблачные перспективы Microsoft

    4 декабря

  • Лекция С.Д.Кузнецова Понятие модели данных. Обзор разновидностей моделей данных

    CITKIT.ru:

  • OpenSolaris 2008.11 Release. Первые впечатления

  • Linux vs FreeBSD: продолжим "Священные войны"?

  • Nokia N810 as is

  • Индульгенция для FOSS

  • Друзья СПО'2008

    26 ноября

  • Нечеткое сравнение коллекций: семантический и алгоритмический аспекты

    CitCity:

    CITKIT.ru:

  • Глава из книги А.Федорчука
    Сага о FreeBSD:
  • 19 ноября

  • Проблемы экономики производства крупных программных продуктов

  • Язык модификации данных формата XML функциональными методами

    CITKIT.ru:

  • Главы из книги А.Федорчука
    Сага о FreeBSD:

    Заметки к книге:

  • FreeBSD: монтирование сменных устройств и механизм HAL
  • Текстовый редактор ee

    12 ноября

  • Правило пяти минут двадцать лет спустя, и как флэш-память изменяет правила (Гоц Грейф, перевод: Сергей Кузнецов)

    CITKIT.ru:

  • Главы из книги А.Федорчука
    Сага о FreeBSD:
  • OSS в России: взгляд правоведа (В.Житомирский)

  • Новая статья из цикла С.Голубева "Железный марш":

    29 октября

  • О некоторых задачах обратной инженерии

  • Веб-сервисы и Ruby

  • Тестирование web-приложений с помощью Ruby

    CITKIT.ru:

  • Главы из книги А.Федорчука
    Сага о FreeBSD:

  • PuppyRus Linux - беседа с разработчиком (С.Голубев)

  • Сергей Кузнецов. Заметка не про Linux

    22 октября

  • Обзор методов описания встраиваемой аппаратуры и построения инструментария кросс-разработки

    CITKIT.ru:

  • Сергей Кузнецов. Почему я равнодушен к Linux

  • Глава из книги А.Федорчука
    Сага о FreeBSD:
  • Что надо иметь
    3. Базовые познания

    CitCity:

  • Управление IT-инфраструктурой на основе продуктов Microsoft

    15 октября

  • Методы бикластеризации для анализа интернет-данных

    CitCity:

  • Разъемы на ноутбуках: что они дают и зачем их так много?
  • AMD Puma и Intel Centrino 2: кто лучше?

    CITKIT.ru:

  • Новый цикл статей С.Голубева
    Железный марш:

  • Главы из книги А.Федорчука
    Сага о FreeBSD:

    8 октября

  • Автоматизация тестирования web-приложений, основанных на скриптовых языках
  • Опыт применения технологии Azov для тестирования библиотеки Qt3

    Обзоры журнала Computer:

  • SOA с гарантией качества
  • Пикоджоуль ватт бережет
  • ICT и всемирное развитие

    CitCity:

  • Пиррова победа корпорации Microsoft

    CITKIT.ru:

  • Главы из книги А.Федорчука
    Сага о FreeBSD:

    Статья из архива:

  • Я живу в FreeBSD (Вадим Колонцов)

    Новые Блогометки:

  • Перекройка шаблона Blogger или N шагов к настоящему
  • Blogger. Comment style
  • Screenie или глянцевый снимок экрана

    2 октября

    CITKIT.ru:

  • Сага о FreeBSD (А. Федорчук)

    Zenwalk: пакет недели

  • Банинг — интеллектуальное развлечение (С.Голубев)

    CitCity:

    25 сентября

  • Клермонтский отчет об исследованиях в области баз данных

    CITKIT.ru:

  • Пользователям просьба не беспокоиться... (В.Попов)

  • Снова про ZFS: диск хорошо, а два лучше
  • Командная оболочка tcsh (А.Федорчук)

    Zenwalk: пакет недели

    17 сентября

  • T2C: технология автоматизированной разработки тестов базовой функциональности программных интерфейсов
  • Технология Azov автоматизации массового создания тестов работоспособности

    CITKIT.ru:

  • FreeBSD: ZFS vs UFS, и обе-две — против всех (А.Федорчук)

    Zenwalk: пакет недели

  • Дачнет — практика без теории (С.Голубев)

    10 сентября

  • За чем следить и чем управлять при работе приложений с Oracle
  • Планировщик заданий в Oracle
    (В.Пржиялковский)

    CITKIT.ru:

  • Microsoft: ответный "боян" (С.Голубев)

  • Причуды симбиоза, или снова "сделай сам" (В.Попов)

  • Файловые системы современного Linux'а: последнее тестирование
  • Zsh. Введение и обзор возможностей
    (А.Федорчук)

    Описания пакетов Zenwalk: Zsh, Thunar, Thunar-bulk-rename, Xfce4-places-plugin, Xfce4-fsguard-plugin

    Блогометки:

  • Google Chrome
  • Лончер для ASUS Eee PC 701

    3 сентября

    CITKIT.ru:

  • Заметки о ядре (А.Федорчук):

    Добавлены описания пакетов Zenwalk: Galculator, Screenshot, Gnumeric, Pidgin

    В дискуссинном клубе:

  • И еще о Википедии и Google Knol

  • Лекция для начинающего линуксоида (С.Голубев)

    26 августа

  • Транзакционная память (Пересказ: С. Кузнецов)

    CITKIT.ru:

  • Открыт новый проект Zenwalk: пакет недели

  • Статья Текстовые процессоры и их быстродействие: конец еще одной легенды?

    21 августа

    CITKIT.ru:

  • Почему школам следует использовать только свободные программы (Ричард Столлман)
  • Беседа Сергея Голубева с учителем В.В.Михайловым

  • Википедия или Гуглезнание? Приглашение к обсуждению (Алексей Федорчук)
  • Народная энциклопедия от Google (StraNNik)

  • Обзор Mandriva 2009.0 Beta 1 Thornicrofti
  • Новичок в Линукс: Оптимизируем Mandriva 2008.1

  • Книга Zenwalk. Приобщение к Linux:

    13 августа

    CitCity:

  • Мирный Atom на службе человеку. Обзор платы Intel D945GCLF с интегрированным процессором
  • Обзор процессоров Intel Atom 230 на ядре Diamondville

  • iPhone - год спустя. Скоро и в России?

    CITKIT.ru:

  • Интермедия 3.4. GRUB: установка и настройка (из книги Zenwalk. Приобщение к Linux)

    6 августа

  • СУБД с хранением данных по столбцами и по строкам: насколько они отличаются в действительности? (Пересказ: С. Кузнецов)

    CITKIT.ru:

  • Интермедия 2.2. Что неплохо знать для начала (из книги Zenwalk. Приобщение к Linux)

  • И снова про шрифты в Иксах (А.Федорчук)

  • 20 самых быстрых и простых оконных менеджеров для Linux

  • Дело о трех миллиардах (С.Голубев)

    30 июля

  • OLTP в Зазеркалье (Пересказ: С. Кузнецов)

    CitCity:

  • Будущее BI в облаках?
  • Тиражные приложения и заказная разработка. Преимущества для заказчика
  • Дискуссия со сторонниками заказной разработки

    CITKIT.ru:

  • Новые главы книги Zenwalk. Приобщение к Linux:
  • Глава 8. Пакеты: средства установки, системы управления, системы построения
  • Глава 9. Zenwalk: репозитории, пакеты, методы установки

    23 июля

    CITKIT.ru:

  • Все против всех. 64 vs 32, Intel vs AMD, tmpfs vs ext3
  • Две головы от Intel

  • Zenwalk: обзор штатных приложений (глава из книги "Zenwalk. Приобщение к Linux")

  • Нормально, Григорий...

    16 июля

    Обзоры журнала Computer:

  • Перспективы и проблемы программной инженерии в XXI веке
  • Большие хлопоты с большими объемами данных
  • Перспективы наноэлектроники

    CITKIT.ru:

  • Интермедия о лицензиях (А.Федорчук. "Zenwalk. Приобщение к Linux")

  • Есть ли будущее у KDE?

  • Linux в школе: альтернативный вариант в задачах

  • Шифр (приключения агента Никодима)

    10 июля

    CITKIT.ru:

  • Новые разделы книги А. Федорчука Zenwalk. Приобщение к Linux:
  • Интермедия вступительная. Linux или GNU/Linux? Как вас теперь называть?
  • Глава 5. Среда Xfce
  • Глава 6. Xfce: приложения и плагины

  • ZUR (Zenwalk User Repository) FAQ

    2 июля

  • Персистентность данных в объектно-ориентированных приложениях (С. Кузнецов)

    CITKIT.ru:

  • Новые разделы книги А. Федорчука Zenwalk. Приобщение к Linux:
  • Интермедия 1.2. Дорога к Zenwalk'у. Период бури и натиска
  • Интермедия 3.3. Немного о Linux'е и "железе"
  • Глава 4. Настройка: инструментами и руками
  • Интермедия 4.1. Zenpanel и конфиги: поиски корреляции

  • Интервью с Жан-Филиппом Гийоменом, создателем дистрибутива Zenwalk

  • Linux в школе: первые итоги (С. Голубев)

    25 июня

    CITKIT.ru:

  • Zenwalk. Приобщение к Linux (А. Федорчук)

  • Логика и риторика (С.Голубев)

  • Технология Tru64 AdvFS

  • Ханс Райзер предлагает отвести полицейских к телу Нины

    18 июня

  • Проекты по управлению данными в Google (Пересказ: С. Кузнецов)

    CITKIT.ru:

  • ОС и поддержка "железа": мифы и реальность (А. Федорчук)

  • Linux в школе: другие дистрибутивы

  • Пинок (С. Голубев)

    4 июня

  • Ландшафт области управления данными: аналитический обзор (С. Кузнецов)

    CITKIT.ru:

  • Linux в школе: слово заинтересованным лицам

  • SlackBuild: пакеты своими руками

  • Linux от компании Novell. Установка и обзор openSUSE Linux

    Все публикации >>>




  • IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

    Информация для рекламодателей PR-акции, размещение рекламы — тел. +7 495 4119920, ICQ 232284597 Пресс-релизы — pr@citcity.ru
    Послать комментарий
    Информация для авторов
    Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
    Copyright © 1997-2000 CIT, © 2001-2007 CIT Forum
    Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...