---
title: "Collada Parser своими руками."
description: "В первой статье я б хотел поведать о том с чего впервую очередь начинает gamedever, а именно с вопр..."
author: "KazGameDever"
published: "2011-03-26T03:04:22+00:00"
modified: "2011-03-26T04:02:20+00:00"
locale: "ru"
canonical_url: "https://yvision.kz/post/collada-parser-svoimi-rukami-135548"
markdown_url: "https://yvision.kz/post/collada-parser-svoimi-rukami-135548/markdown"
site_name: "Yvision.kz"
---

# Collada Parser своими руками.

> В первой статье я б хотел поведать о том с чего впервую очередь начинает gamedever, а именно с вопр...

![Collada Parser своими руками.](https://storage.yvision.kz/images/user/kazgamedever/lmVw1dqxEwce7M1hQRc3k3le8UDLd9.jpg)

В первой статье я б хотел поведать о том с чего впервую очередь начинает gamedever, а именно с вопроса: "Какой выбрать формат файла, для экспорта 3d модлей в свою игру ?". К примеру 3ds max экспортирует сцену в такие форматы как 3ds, obj, wvf и т. д. ... Но перепробывав все форматы макса я понял что с ними работать начинающему gamedever-y очень сложно. В сети я нашел очень удобный и распростарненный формат Collada - его простота в первую очередь вызвана тем что этот формат хранит данные по 3d модели в текстовом варианте - в XML.

Plugin Collada для 3ds Max 2009 32 bit + Спецификация Collada (ENG) можете скачать [здесь](http://letitbit.net/download/09311.0849e10bc812c781a3030cf5ce93/Collada_Max.rar.html).

Итак: сегодня я расскажу как удобней и проще прочитать файл collada, и приведу примеры своего кода.

**Collada Parser:**

Как сделать Collada Parser своими руками ? Какой алгоритм самый оптимальный ? Что представляет собой файл Collada *.dae ? Вот о чем сейчас я хочу более подробно рассказать.

Конечно же, уже существуют такие парсеры как Collada DOM и т.д., но покопавшись в мануале этого парсера я понял, что нужно убить как минимум 5-6 дней что бы понять как работает тот же DOM. В связи с этим решил написать свой парсер конечно же менее функциональный, но зато он экспортирует те данные, которые мне действительно неободимы, а не все то что выплёвывает 3ds max при экспорте в Colllada.

Однако я не стану объяснять как и каким образом хранятся данные в файле коллада(в этом вам поможет *спецификация*), как храниться та же скелетная анимация, что за матрицы предоставляет нам формат Collada и как их перемножать на вершины… всё это выходит за рамки данной статьи, я обязательно постараюсь обо всем этом написать в следующих статьях ! Однако в этой статье пойдет речь именно о формате Collada и о том, как проще и оптимальней с него считать данные и использовать в работе.

Я упомянул о том, что мой двиг имеет возможность экспортирования данных из файла *.dae (Collada) – это и был парсер встроенный в мой двиг. Однако всё дальше углубляясь в программирование своей игры, я понял, что наличие данной возможности в двиге игры совсем не обязательно, и даже неудобно. Хотя на начальных стадиях разработки движка было очень удобно вносить корректировки и добавлять новые функции для моего парсера, но чем дальше тем больше он начал жрать памяти, так как появлялись данные, которые я просто боялся удалить ! А вдруг чего-нить перестанет работать и тогда будет ой как сложно найти дырку в коде, да и ещё её и залатать. :(

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

Collada *.dae - это открытый стандарт файлов для интерактивных 3D приложений базирующийся на формате XML. Из этих слов, важными для нас являются «базирующийся на формате XML». Что такое XML - (англ. eXtensible Markup Language — расширяемый язык разметки; произносится [экс-эм-э́л]) — рекомендованный Консорциумом Всемирной паутины язык разметки, фактически представляющий собой свод общих синтаксических правил. XML — текстовый формат, предназначенный для хранения структурированных данных (взамен существующих файлов баз данных), для обмена информацией между программами, а также для создания на его основе более специализированных языков разметки (например, XHTML), иногда называемых словарями. XML является упрощённым подмножеством языка SGML. (Wikipedia.ru)

Кому нравиться так, а по мне так Collada *.dae – Это текстовый файл, легко читаемый (ибо текстовый :) ), состоящий в виде иерархии, где имеються родительские блоки родственные блоки и блоки дети, которые в свою очередь имеют родителей, детей, родственников и т. д. (более подробно см. далее). Блоки имеют заголовок (хидер) и конец к примеру главный и самый корневой блок Collada, который начинается с заголовка (хидера)  и конца . Я думаю комментарии излишне, сразу же видно в чем разница между заголовком и концом. Однако очень часто в заголовке содержаться дополнительные поля, в которых описано, что храняться между началом и концом данного блока.

Вот такой вид у файла Collada *.dae

-
-
-
-
- %CC%E0%F0%E0%F2
- 3dsMax 9 - Feeling …
- ColladaMax Export Options…;
- file:///E:/My%...
-
- 2010-09-12T13:44:09Z
- 2010-09-12T13:44:10Z
-
- Z_UP
-
-
-
-
-
-

В самом начале файла идет заголовок  <хml> , который гласит о версии файла, и его кодировке, далее начинается иерархия "блоков данных" файла Collada. Самый главный и самый корневой блок это  <СOLLADA> , он является основным и не имеет не родителей не родных, только детей, а именно блок  <аsset> , и блок  <librаry_аnimations> . Которые в свою очередь имеют одинакового родителя это блок  <СOLLADA> , они являются родственниками, и имеют своих детей. Так к примеру блок  <аsset>  имеет детей:  <contributor>, <created>, <modified>, <unit>, <up_axis>.

До конца статьи я буду называть все блоки именно родителями, детми, и родственниками. :)

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

** **Определения некторых терминов используемых мною в статье:

Пример: ****

- «Хидер» - это полностью выше указанная строка.

- «Имя хидера» - это слово COLLADA в выше указанной строке.

- «Поля блока» - это xmlns="…" или version="…".

- «Блок» - это информация между хидером  и концом

Итак, этого достаточно, что бы приступить к программированию нашего парсера Collada.

##### **Алгоритм**:

Как мы уже видели файл состоит из блоков родителей детей и родственников, поэтому самый оптимальный вариант для считывания файла это "**рекурсия**". Выглядит это примерно так: мы взываем функцию CreateHierarhy(*block) коротая вначале записывает данные блока *block который мы передаем в параметрах, потом проверяет на наличие детей, если есть то вызывает саму себя CreateHierarhy(*block_child), если нет то проверяет наличие родственников и если есть то считывает их CreateHierarhy(*block_sibling), если нет то возвраещается на более высшую ступень вызывая **return**.

Всё ! Но это если вкратце. А если детально то смотрим код и читаем комментарии:

Открываем файл *.dae:
- string buf;//глобальный массив данных !!!

- string FileName=”SomeAlseFile.dae”;

- /*Получаем размер файла в байтах*/

- [FILE](http://www.php.net/file) *an;
- if (!(an=[fopen](http://www.php.net/fopen)(FileName.c_str(),"rb")))

- {MessageBox(0,"Ошибка открытия файла 1!",0,0); return false;}

- [fseek](http://www.php.net/fseek)(an,0,SEEK_END);
- eof=[ftell](http://www.php.net/ftell)(an); //Найдем конец файла

- [fclose](http://www.php.net/fclose)(an);

- /*-------------------------------*/

- /*Читаем файл*/

- [FILE](http://www.php.net/file) *in;
- if (!(in=[fopen](http://www.php.net/fopen)(FileName.c_str(),"r")))

- {MessageBox(0,"Ошибка открытия файла 2!",0,0); return false;}

- char* Buffer;
- Buffer=new char[eof];

- [fread](http://www.php.net/fread)(Buffer,eof,1,in);//Считывам в буффер все данные файла.

- buf=string(Buffer);

- [fclose](http://www.php.net/fclose)(in);
- delete Buffer;

Немного странное нахождение конца файла, однако у меня всё работает. так как UTF-8 - это 1 символ - 1 байт. Так же хочу отметить, что я работаю с std::string так как это очень удобно, и практично, хоть и занимает больше процессорного времени. Но скорость здесь нам особой помехи не представляет мы же не рендерим ? :)

Выше мы открываем файл и считываем всё, что там есть, в память, или в переменную типа string “**buf**” - это глобальный массив, в котором будут храниться все символьные данные файла.

Далее нам необходима функция поиска символа или слова в buf. В библиотеке *std* есть функция поиска в массиве string но всё же я предпочел немного её откорректировать. Для нас так удобней.

- int ParseDae2::find(string str,int point_of_start,int point_of_end)

- {

- int i=buf.find(str,point_of_start);//::std функция поиска в string-e

- if (ipoint_of_end)

- {

- return 0;

- }

- else

- {

- return i;

- }

- }

Функция в параметрах принимает строку/символ который необходимо найти, номер символа с которого нужно начать поиск, номер символа на котором нужно остановить поиск (эти номера символов в массиве **buf**). А возвращает номер символа в массиве buf с которого, начинается искомое слово.

*Пример:* "**М**амы мыла ра**м**у" - find("мыла", 0, 12);
- вернет 6, искать будет с "**М**" до "**м**".

Дальше нам необходимо каждый блок в файле представить в виде структуры данных. Что бы потом с ней можно было работать.

Назовем эту структуру - **Block**:

- //Это моя структура блока на сегодняшний день

- //в будущем, я думаю, она будет побольше, но на данный

- //момент это все поля, которые мне необходимы

- struct Block

- {

- //Точка начала данных блока,

- //или позиция указателя на начало данных

- int point_start_data;

- //Точка конца блока, именно БЛОКА !,

- //или позиция указателя на конец блока

- int point_end_block;
- bool IsEndble;//Флаг описывающий блок (конечный/одинарный) это такой  или такой

- //…

- //Поля блока:
- string [Header](http://www.php.net/header); //Хидер блока или его название к примеру

- string texture; //поле - Текстура..

- string Name;//поле - Имя

- string id;//поле – id

- string sid;
- string material;// материал и т.д. …

- //…

- //Указатели

- Block* bSibling; //Указатель на родственника

- Block* bChild; //Указатель на дитя

- Block* bParent; //Указатель на родителя

- };

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

Следующая функция самая **главная** – **findblock**(); **Функция не ищет какой-то определенный блок в файле !** Она **находит**/**инициализирует** первый попавшийся ей по пути блок. В параметрах данной функции мы передаем позицию начала поиска и конца поиска (позиции массива buf). На основании этих параметров функция находит первый блок в данном диапазоне буфера данных buf, и заполняет все поля данных выше указанной структуры.

При написании это функции нужно учесть все подводные камни это:
- То что в блок могут входить одноименные блоки, т.е. с тем же именем хидера только внутри искомого.

- Блоки бывают 2 видов "одинарные" и "конеченые".

- Block* findblock (int pOfStart,int pOfEnd)

- {

- int s4=1;//счетчик

- int Num;//счетчик кол-ва

- Block* block;//объявим блок

- block= new Block();//выделим память

- //Начнем с того что всё путем

- block->find=true;
- string [header](http://www.php.net/header);

- //Получим header

- int tmp=find("find=false; return block;}

- //Если найдено то переместим курсор с “Header=[header](http://www.php.net/header);

- [header](http://www.php.net/header).insert(0,"find=false;return block;}

- //Перемещаем указатель на символ после header-a.

- tmp=posOfStartHeader+[header](http://www.php.net/header).length();

- //Найдем конец header-a

- int posOfEndHeader=find(">",posOfStartHeader+1,pOfEnd);

- //проверка

- if (posOfEndHeader==0)

- {

- MessageBox(0,"posOfEndHeader=0","function findblock2",0);
- block->find=false; return block;

- }

- //Получим point_start_data т.е. позицию указателя

- //за “>”-концом header-a, а так же проверим «одинарный» ли

- //блок или «конечный» ?

- if (buf[posOfEndHeader-1]=='/')

- {

- block->point_start_data=posOfEndHeader;
- block->IsEndble=false;//одинарный

- }

- else

- {

- block->point_start_data=posOfEndHeader+1;
- block->IsEndble=true;//конечный

- }

- //Получим id

- int [pos](http://www.php.net/pos)=find("id=",posOfStartHeader,posOfEndHeader);
- if ([pos](http://www.php.net/pos)==0)

- {block->id="NULL";}

- else

- {[pos](http://www.php.net/pos)+=4;
- Num=GetNumSimbols([pos](http://www.php.net/pos));
- block->id=GetStr([pos](http://www.php.net/pos),Num-1);}//id

- //Получим sid

- [pos](http://www.php.net/pos)=find("sid=",posOfStartHeader,posOfEndHeader);
- if ([pos](http://www.php.net/pos)==0)

- {block->sid="NULL";}

- else

- {[pos](http://www.php.net/pos)+=5;
- Num=GetNumSimbols([pos](http://www.php.net/pos));
- block->sid=GetStr([pos](http://www.php.net/pos),Num-1);}//sid

- //Получим material

- [pos](http://www.php.net/pos)=find("material=",posOfStartHeader,posOfEndHeader);
- if ([pos](http://www.php.net/pos)==0)

- {block->material="NULL";}

- else

- {[pos](http://www.php.net/pos)+=10;
- Num=GetNumSimbols([pos](http://www.php.net/pos));
- block->material=GetStr([pos](http://www.php.net/pos),Num-1).c_str();} //material

- //Получим point_end_block – позицию указателя

- //на конец блока. По правде говоря тут по сложнее

- //так как в блок может входить не определенное

- //кол-во одинаковых блоков ! к примеру такие блоки

- //как  и т.д. я реализовал обход

- //даннной проблемы так:
- if (block->IsEndble) //если блок закан-ся на  то тогда:

- {

- int NumStarts;//количество «стартов» :)

- int endpos;//позиция конца

- string StartBlock;//Имя хидера начального блока

- StartBlock=[header](http://www.php.net/header);//Запомним наш хидер

- [header](http://www.php.net/header).insert(1,"/");//Доб 2 символ

- [header](http://www.php.net/header).append(">");//Доб посл символ

- [pos](http://www.php.net/pos)=find([header](http://www.php.net/header),block->point_start_data,pOfEnd);

- //Следующая функция находит количество блоков с

- //одноименным названием, которые входят в этот

- //блок. Функция возвращает значение только дойдя

- //до конца заданного диапозоноа.

- //Получаем кол-во «стартов»

- NumStarts=getNumBlocks(StartBlock,block->point_start_data,[pos](http://www.php.net/pos));

- [pos](http://www.php.net/pos)+=[header](http://www.php.net/header).length()-1;

- //Если есть одноименные блоки

- if (NumStarts!=0)

- {

- while (NumStarts!=0)//Запускаем цикл

- {

- //уменьшаем кол-во стартов (блока)

- NumStarts--;

- //Ищем конец блока

- endpos=find([header](http://www.php.net/header),[pos](http://www.php.net/pos),pOfEnd);

- //Проверяем были ли старты ? и

- //прибовляем к общему количеству

- NumStarts+=getNumBlocks(StartBlock,[pos](http://www.php.net/pos),endpos);

- //устанавливаем pos в позицию

- //за последним найденным концом блока

- endpos+=[header](http://www.php.net/header).length()-1;

- [pos](http://www.php.net/pos)=endpos;

- }

- }

- //В конце концов присваем позицию конца блока.

- block->point_end_block=pos-([header](http://www.php.net/header).length());

- }

- else //Иначе если блок одниарный:

- {

- block->point_end_block=posOfEndHeader;

- }

- block->find=true;
- return block;

- }

В функции выше, встречаются функции **getNumBlocks**(),**GetNumSimbols**(),и **GetStr**() - описание этих функций смотрим ниже:

- //Функция возвращает количество блоков с хидером NameBlock

- //в диапозоне с pOfStart до pOfEnd.

- int getNumBlocks(string NameBlock,int pOfStart,int pOfEnd)

- {

- int flag=1;
- int NumBlocks=0,[pos](http://www.php.net/pos);

- [pos](http://www.php.net/pos)=pOfStart;
- while (flag)

- {

- [pos](http://www.php.net/pos)=find(NameBlock.c_str(),[pos](http://www.php.net/pos),pOfEnd);
- if (pos>0) {NumBlocks++;}

- else

- {

- flag=0;
- break;

- }

- [pos](http://www.php.net/pos)+=NameBlock.length();

- }

- return NumBlocks;

- }

- //Функция возвращает количество символов

- //с позиции pos, до пробела, и знаков

- //больше или равно см ниже.

- int GetNumSimbols(int i)

- {

- int Num=0, t=1;
- while(t==1)

- {

- if (buf[i]==' ' || buf[i] == '')

- {

- t=0;
- break;

- }

- i++;
- Num++;

- }

- if (t!=0) {return 0;}

- return Num;

- }

- //Функция возвращает строку типа string

- //из нашего глобального буфера buf.

- //С позиции “i”.

- string GetStr(int i,int NumSimbols)

- {

- string str;
- for (int a=0;abChild=findblock(mb->point_start_data,mb->point_end_block);
- if (mb->bChild->find) //если найдено то рекурсивно вызываем функцию

- {

- CreateHierarchy(mb->bChild,mb->bChild->point_start_data,mb->point_end_block);

- }

- else //Иначе блок «дитя» не существует.

- {mb->bChild=NULL;}

- //Определим точку конца блока:
- int pointostart;

- //Если блок «конечный» то точка поиск родителя надо начинать

- //с point_end_block+длина имени хидреа+3 символа (это “/”,””)

- //проще говоря point_end_block указывает на первый символ, конца блока:

- // то point_end_block указывает на символ “IsEndble) {pointostart=mb->point_end_block+mb->Header.length()+3;}

- //Иначе если он «одинарный» то:
- else {pointostart=mb->point_end_block+1;}

- //А теперь вызовем поиск блока родственника

- mb->bSibling=findblock(pointostart,pOfEnd);

- //и то же самое что и с дитем:
- if (mb->bSibling->find) //если найдено то рекурсивно вызываем функцию

- {

- CreateHierarchy(mb->bSibling,mb->bSibling->point_start_data,pOfEnd);

- }

- else //Иначе блок «родственника» не существует.

- {

- mb->bSibling=NULL;
- return;//Возвращем.

- }

- }

Вот вобщемто и всё ! Все основные функции готовы осталось только, найти главный блок Collada и вызвать функцию CreateHierarhy(), передав в параметре этот блок. После чего функция рекурсивно переберет весь файл, и разложит его пополочкам, в виде древа: дитя сосед родитель.

Опять вернемся к началу:

- //-------------------------------------это вы видели в начале-----

- string buf;//глобальный массив данных !!!

- string FileName=”SomeAlseFile.dae”;

- /*Получаем размер файла в байтах*/

- [FILE](http://www.php.net/file) *an;
- if (!(an=[fopen](http://www.php.net/fopen)(FileName.c_str(),"rb")))

- {MessageBox(0,"Ошибка открытия файла 1!",0,0); return false;}

- [fseek](http://www.php.net/fseek)(an,0,SEEK_END);
- eof=[ftell](http://www.php.net/ftell)(an); //Найдем конец файла

- [fclose](http://www.php.net/fclose)(an);

- /*-------------------------------*/

- /*Читаем файл*/

- [FILE](http://www.php.net/file) *in;
- if (!(in=[fopen](http://www.php.net/fopen)(FileName.c_str(),"r")))

- {MessageBox(0,"Ошибка открытия файла 2!",0,0); return false;}

- char* Buffer;
- Buffer=new char[eof];

- [fread](http://www.php.net/fread)(Buffer,eof,1,in);//Считывам в буффер все данные файла.

- buf=string(Buffer);

- [fclose](http://www.php.net/fclose)(in);
- delete Buffer;

- //-------------------------------------это вы видели в начале-----

- //далее..

- Block *bCollada;
- bCollada=new Block();
- bCollada->point_start_data=find(“”,0, eof);
- CreateHierarchy(bCollada,bCollada->point_start_data,eof);

**Дальше - больше** к примеру вы можете создать функцию поиска в только, что созданном древе, блока по имени хидера:
- Block* GetBlockH(string [header](http://www.php.net/header), ParseDae2::Block* mb)

- {

- Block* bl;
- if (![strcmp](http://www.php.net/strcmp)([header](http://www.php.net/header).c_str(),mb->Header.c_str()))

- {return mb;}

- if (mb->bChild!=NULL)

- {

- bl=GetBlockH([header](http://www.php.net/header),mb->bChild);
- if (bl!=NULL) {return bl;}

- }

- if (mb->bSibling!=NULL)

- {

- bl=GetBlockH([header](http://www.php.net/header),mb->bSibling);
- if (bl!=NULL) {return bl;}

- }

- return NULL;

- }

Или тоже самое, но по id блока и т. д.

Вот теперь, я думаю, действительно ВСЁ ! Надеюсь мои старанья в написании этой статьи действительно окажутся для Вас продуктивными.

Копипаст тут никак не поможет. Дело в том что этой статей я хотел поведать читателю более оптимальный вариант для реализации Parsera.

Спасибо за то, что зашли ;) **С Уважением m21448 !**

** **

---

Source: [https://yvision.kz/post/collada-parser-svoimi-rukami-135548](https://yvision.kz/post/collada-parser-svoimi-rukami-135548)