Назад | Содержание | Вперед
Данный формат был выбран как наиболее удобный в силу следующих причин:
Сам по себе формат достаточно прост, и загрузка не должна представлять сложностей (мне, например, помог опыт написания сканера компилятора языка "O" на втором курсе). Нужно помнить, что в 3ds max принята правая система координат (если ось X направлена вправо, а Y вверх, то Z на наблюдателя).
Рассмотрим наиболее важные части файла (здесь и далее оставлены только наиболее важные фрагменты файла).
*MATERIAL 0 { *MATERIAL_NAME "01 - Default" *MATERIAL_CLASS "Standard" *MATERIAL_AMBIENT 0.5882353187 0.5882353187 0.9333333969 *MATERIAL_DIFFUSE 0.5882353187 0.5882353187 0.9333333969 *MATERIAL_SPECULAR 0.8999999762 0.8999999762 0.8999999762 *MATERIAL_SHINE 0.4499999881 *MATERIAL_SHINESTRENGTH 0.9899999499 *MATERIAL_TRANSPARENCY 0.0000000000 }
Сразу за MATERIAL идет номер. Использование материала определяется по номеру идущему за MATERIAL_REF внутри секции GEOMOBJECT.
Внутри секции перечислены имя, класс, цвет области тени, рассеянный цвет, цвет блика, яркость блика и степень блика (пропорционально степени косинуса в модели освещения Фонга), степень прозрачности (одним числом). К сожалению, стандартные экспортер не сохраняет для трассируемых материалов степень отражения и коэффициент преломления. По этой причине в программу был добавлен редактор материалов.
Также внутри этой секции можно обнаружить список используемых карт. Для нас представляют интерес следующие: MAP_AMBIENT, MAP_DIFFUSE, MAP_SPECULAR, MAP_OPACITY, MAP_BUMP, MAP_REFLECT (на самом деле карта MAP_REFLECT в 3ds max используется для других целей, но можно использовать и как карту степени отражения).
Типы карт:
*MAP_DIFFUSE { *MAP_NAME "Map #1" *MAP_CLASS "Bitmap" *MAP_AMOUNT 1.0000000000 *BITMAP "..\images\DRYLEAVE.JPG" *UVW_U_OFFSET 0.0000000000 *UVW_V_OFFSET 0.0000000000 *UVW_U_TILING 1.0000000000 *UVW_V_TILING 1.0000000000 } *MAP_DIFFUSE { *MAP_NAME "Map #1" *MAP_CLASS "Checker" *MAP_AMOUNT 1.0000000000 }
По MAP_CLASS можно определить тип карты. Для растра сохраняются имя файла с рисунком (русские символы в пути к сожалению обрабатываются неправильно), отступы и масштабирование рисунка.
*NODE_TM { *NODE_NAME "Plane01" *TM_ROW0 0.7007260919 0.0000000000 0.7134304047 *TM_ROW1 0.0000000000 1.0000000000 0.0000000000 *TM_ROW2 -0.7134304047 0.0000000000 0.7007260919 *TM_ROW3 0.0000000000 0.0000000000 0.0000000000 }
За перенос отвечает нижняя строка матрицы (*TM_ROW3).
*GEOMOBJECT { *NODE_NAME "Plane01" *NODE_TM { *NODE_NAME "Plane01" *TM_ROW0 0.7007260919 0.0000000000 0.7134304047 *TM_ROW1 0.0000000000 1.0000000000 0.0000000000 *TM_ROW2 -0.7134304047 0.0000000000 0.7007260919 *TM_ROW3 0.0000000000 0.0000000000 0.0000000000 } *MESH { *TIMEVALUE 0 *MESH_NUMVERTEX 4 *MESH_NUMFACES 2 *MESH_VERTEX_LIST { *MESH_VERTEX 0 -35.0363044739 -50.0000000000 -35.6715202332 *MESH_VERTEX 1 35.0363044739 -50.0000000000 35.6715202332 *MESH_VERTEX 2 -35.0363044739 50.0000000000 -35.6715202332 *MESH_VERTEX 3 35.0363044739 50.0000000000 35.6715202332 } *MESH_FACE_LIST { *MESH_FACE 0: A: 2 B: 0 C: 3 AB: 1 BC: 0 CA: 1 *MESH_SMOOTHING 1 *MESH_MTLID 0 *MESH_FACE 1: A: 1 B: 3 C: 0 AB: 1 BC: 0 CA: 1 *MESH_SMOOTHING 1 *MESH_MTLID 0 } *MESH_NUMTVERTEX 8 *MESH_TVERTLIST { *MESH_TVERT 0 0.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 1 1.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 2 0.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 3 1.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 4 0.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 5 1.0000000000 0.0000000000 0.0000000000 *MESH_TVERT 6 0.0000000000 1.0000000000 0.0000000000 *MESH_TVERT 7 1.0000000000 1.0000000000 0.0000000000 } *MESH_NUMTVFACES 2 *MESH_TFACELIST { *MESH_TFACE 0 6 4 7 *MESH_TFACE 1 5 7 4 } *MESH_NORMALS { *MESH_FACENORMAL 0 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 2 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 0 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 3 0.0000000000 0.0000000000 1.0000000000 *MESH_FACENORMAL 1 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 1 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 3 0.0000000000 0.0000000000 1.0000000000 *MESH_VERTEXNORMAL 0 0.0000000000 0.0000000000 1.0000000000 } } *PROP_MOTIONBLUR 0 *PROP_CASTSHADOW 1 *PROP_RECVSHADOW 1 *MATERIAL_REF 0 }
Основной интерес представляет секция MESH. В ней определено количество вершин (MESH_NUMVERTEX), количество треугольников (MESH_NUMFACES), текстурных координат (MESH_NUMTVERTEX).
В секции MESH_VERTEX_LIST по порядку идут MESH_VERTEX, затем номер текущей вершины (от 0 до MESH_NUMVERTEX - 1), и три числа x, y, z, в относительной системе координат (СК).
В MESH_FACE_LIST определены треугольники. За MESH_FACE идет номер текущего треугольника, двоеточие ":", затем "A:" и номер первой вершины в списке MESH_VERTEX_LIST, аналогично "B:" и "C:", затем идут флаги видимости ребер треугольника (1 - видимо, 0 - нет), MESH_SMOOTHING определяет группу сглаживания для треугольника (следует учитывать, что если группа сглаживания не одна, номера должны разделятся ","), а MESH_MTLID номер субматериала, если используется.
MESH_TVERTLIST определяет список текстурных координат вершин, аналогично MESH_VERTEX_LIST, но с текстурными координатами.
С помощью MESH_TFACELIST загружаются текстурные координаты. После MESH_TFACE идет номер треугольника, затем три индекса вершин в списке текстурных координат.
MESH_NORMALS определяет нормали в вершинах треугольника. За идентификатором MESH_FACENORMAL идет номер треугольника, его нормаль (x, y, z), затем три раза по порядку MESH_VERTEXNORMAL, включающего индекс вершины и нормаль в ней (x, y, z).
MATERIAL_REF определяет номер материала в списке MATERIAL_LIST, использующегося сеткой.
*LIGHTOBJECT { *NODE_NAME "Light01" *LIGHT_TYPE Directional *NODE_TM { *NODE_NAME "Light01" *TM_ROW0 0.9999999404 0.0000000000 0.0000000000 *TM_ROW1 0.0000000000 0.9999999404 0.0000000000 *TM_ROW2 0.0000000000 0.0000000000 0.9999999404 *TM_ROW3 0.0000000000 0.0000000000 11.8617439270 } *NODE_TM { *NODE_NAME "Light01.Target" *TM_ROW0 0.9999999404 0.0000000000 0.0000000000 *TM_ROW1 0.0000000000 0.7071066499 0.7071067691 *TM_ROW2 0.0000000000 -0.7071067691 0.7071066499 *TM_ROW3 0.0000000000 0.0000000000 0.0000000000 } *LIGHT_SPOTSHAPE Circle *LIGHT_SETTINGS { *TIMEVALUE 0 *LIGHT_COLOR 1.0000000000 1.0000000000 1.0000000000 *LIGHT_INTENS 1.0000000000 *LIGHT_HOTSPOT 0.5000000000 *LIGHT_FALLOFF 45.0000000000 } }
LIGHT_TYPE определяет тип источника света и может быть: Omni - точечный, Target - нацеленный, Directional - направленный, Free - свободный. Если источник света имеет мишень, то ее матрицу преобразования и анимацию можно получить обнаружив имя источника света с окончанием ".Target". По LIGHT_SPOTSHAPE можно определить форму освещения для направленных источников света: в виде круга (Circle) или прямоугольника (Rect). Внутри LIGHT_SETTINGS сохраняются настройки источника, как то цвет (LIGHT_COLOR), интенсивность (LIGHT_INTENS), для направленных область яркости (LIGHT_HOTSPOT) и спада (LIGHT_FALLOFF) в градусах или метрических единицах, соответственно для нацеленного и направленного. Анимация настроек также сохраняется.
*CAMERAOBJECT { *NODE_NAME "Camera01" *CAMERA_TYPE Target *NODE_TM { *NODE_NAME "Camera01" *TM_ROW0 0.0000000000 1.0000000000 0.0000000000 *TM_ROW1 -0.5039737225 0.0000000000 0.8637189865 *TM_ROW2 0.8637189865 0.0000000000 0.5039737225 *TM_ROW3 100.0000000000 0.0000000000 58.3492660522 } *NODE_TM { *NODE_NAME "Camera01.Target" *TM_ROW0 1.0000000000 0.0000000000 0.0000000000 *TM_ROW1 0.0000000000 1.0000000000 0.0000000000 *TM_ROW2 0.0000000000 0.0000000000 1.0000000000 *TM_ROW3 0.0000000000 0.0000000000 0.0000000000 } *CAMERA_SETTINGS { *TIMEVALUE 0 *CAMERA_NEAR 0.0000000000 *CAMERA_FAR 1000.0000000000 *CAMERA_FOV 0.7853956223 *CAMERA_TDIST 115.7783966064 } }
Камеры как и источники света могут быть нацеленными и свободными. Для нацеленной камеры матрица преобразования и анимация мишени определяется по NODE_NAME, оканчивающемуся на ".Target".
Настройки камеры включают в себя ближний и дальний радиус видимости (CAMERA_NEAR и CAMERA_FAR), область видимости в радианах (CAMERA_FOV), расстояние до цели для свободных камер (CAMERA_TDIST).
Вид камеры находится в отрицательном направлении оси Z, когда в области проекции ось X направлена вправо, а ось Y вверх.
Экспортер может сохранять анимацию матрицы преобразования объектов. Это может быть как набор ключей, так и выборка положений. Второй вариант проще для загрузки.
*TM_ANIMATION { *NODE_NAME "Box01" *CONTROL_POS_TRACK { *CONTROL_POS_SAMPLE 0 -12.9507 21.2540 0.0000 *CONTROL_POS_SAMPLE 160 -11.7180 19.5905 0.0000 *CONTROL_POS_SAMPLE 320 -8.4144 15.1323 0.0000 *CONTROL_POS_SAMPLE 480 -3.6316 8.6780 0.0000 *CONTROL_POS_SAMPLE 640 2.0388 1.0260 0.0000 *CONTROL_POS_SAMPLE 800 8.0050 -7.0253 0.0000 *CONTROL_POS_SAMPLE 960 13.6753 -14.6773 0.0000 *CONTROL_POS_SAMPLE 1120 18.4582 -21.1316 0.0000 *CONTROL_POS_SAMPLE 1280 21.7617 -25.5897 0.0000 *CONTROL_POS_SAMPLE 1440 22.9944 -27.2532 0.0000 } *CONTROL_ROT_TRACK { *CONTROL_ROT_SAMPLE 160 0.3802 -0.3432 0.8589 0.1164 *CONTROL_ROT_SAMPLE 320 0.3177 -0.4181 0.8510 0.3042 *CONTROL_ROT_SAMPLE 480 0.1668 -0.5195 0.8380 0.4201 *CONTROL_ROT_SAMPLE 640 -0.0792 -0.5614 0.8237 0.4664 *CONTROL_ROT_SAMPLE 800 -0.3578 -0.4610 0.8121 0.4539 *CONTROL_ROT_SAMPLE 960 -0.5523 -0.2131 0.8059 0.3977 *CONTROL_ROT_SAMPLE 1120 0.5868 -0.0843 -0.8053 5.9711 *CONTROL_ROT_SAMPLE 1280 -0.5006 0.3111 0.8078 0.2044 *CONTROL_ROT_SAMPLE 1440 -0.4103 0.4187 0.8102 0.0742 } *CONTROL_SCALE_TRACK { *CONTROL_SCALE_SAMPLE 0 1.0000 1.0000 1.0000 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 160 1.0000 1.0000 1.0527 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 320 1.0000 1.0000 1.1939 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 480 1.0000 1.0000 1.3983 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 640 1.0000 1.0000 1.6407 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 800 1.0000 1.0000 1.8957 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 960 1.0000 1.0000 2.1380 -0.0000 -0.0000 -1.0000 0.2287 *CONTROL_SCALE_SAMPLE 1120 1.0000 1.0000 2.3424 -0.0000 -0.0000 -1.0000 0.2115 *CONTROL_SCALE_SAMPLE 1280 1.0000 1.0000 2.4836 0.0000 0.0000 0.0000 0.0000 *CONTROL_SCALE_SAMPLE 1440 1.0000 1.0000 2.5363 0.0000 0.0000 0.0000 0.0000 } }
CONTROL_POS_TRACK содержит выборку положений объекта (нижняя строка матрицы анимации). CONTROL_ROT_SAMPLE включает в себя результирующее вращение, в виде квантериона. CONTROL_SCALE_SAMPLE представляет собой результирующее масштабирование в виде вектора и квантериона.
Для создания загрузчика пришлось основательно погрузиться в исходники экспортера и помощь по 3ds max SDK. Алгоритмы работы с квантерионами были взяты из NVToolkit.
void LoaderASE::ParseNodeTMAnimation(Node *node) { Vector *ap_t = new Vector[scene->GetTotalFrame()]; // The translation components Quat *ap_q = new Quat[scene->GetTotalFrame()]; // The essential rotation Quat *ap_u = new Quat[scene->GetTotalFrame()]; // The stretch rotation. This is the axis system of the scaling application Vector *ap_k = new Vector[scene->GetTotalFrame()]; // The stretch factors. These are the scale factors for x, y and z // инициализируем значениями по умолчанию for (int i = 0; i < scene->GetTotalFrame(); i++) { ap_t[i] = Vector(0.0f, 0.0f, 0.0f); ap_q[i] = Quat(0.0f, 0.0f, 0.0f, 1.0f); ap_u[i] = Quat(0.0f, 0.0f, 0.0f, 1.0f); ap_k[i] = Vector(1.0f, 1.0f, 1.0f); } Check(ID_LCURVE); while (idToken != ID_RCURVE && idToken != ID_NONE) { switch (idToken) { case ID_NODE_NAME: { NextToken(); String name = strToken; Check(ID_STRING); // какой-нибудь "Camera01.Target" if (name != node->name) node = NULL; break; } case ID_POS_TRACK: NextToken(); Check(ID_LCURVE); while (idToken != ID_RCURVE && idToken != ID_NONE) { Check(ID_POS_SAMPLE); int frame = TicksToSceneFrame(GetINumber()); ap_t[frame] = GetVector(); } Check(ID_RCURVE); break; case ID_ROT_TRACK: NextToken(); Check(ID_LCURVE); while (idToken != ID_RCURVE && idToken != ID_NONE) { Check(ID_ROT_SAMPLE); int frame = TicksToSceneFrame(GetINumber()); ap_q[frame] = GetQuat(); } Check(ID_RCURVE); break; case ID_SCALE_TRACK: NextToken(); Check(ID_LCURVE); while (idToken != ID_RCURVE && idToken != ID_NONE) { Check(ID_SCALE_SAMPLE); int frame = TicksToSceneFrame(GetINumber()); ap_k[frame] = GetVector(); ap_u[frame] = GetQuat(); } Check(ID_RCURVE); break; case ID_LCURVE: SkipBlock(); break; default: NextToken(); } } Check(ID_RCURVE); // строим на основе ap_t, ap_q, ap_u, ap_k матрицы if (node) { node->mTransformAnimation = new Matrix[scene->GetTotalFrame()]; Matrix current; Matrix stm, srtm, srtminv, rtm, rtmCurrent, ptmCurrent; MakeIdentity(&rtmCurrent); for (int i = 0; i < scene->GetTotalFrame(); i++) { MakeTranslate(&ptmCurrent, ap_t[i].x, ap_t[i].y, ap_t[i].z); MakeMatrix(&rtm, ap_q[i]); MultMatrixRight(&rtmCurrent, rtm); MakeMatrix(&srtm, ap_u[i]); MakeScale(&stm, ap_k[i].x, ap_k[i].y, ap_k[i].z); ReverseMatrix(&srtminv, srtm); MakeIdentity(¤t); MultMatrixRight(¤t, srtminv); MultMatrixRight(¤t, stm); MultMatrixRight(¤t, srtm); MultMatrixRight(¤t, rtmCurrent); MultMatrixRight(¤t, ptmCurrent); node->mTransformAnimation[i] = current; } } delete ap_t; delete ap_q; delete ap_u; delete ap_k; }
За дополнительной информацией обращайтесь к помощи 3ds max SDK.
Назад | Содержание | Вперед