В предыдущей статье http://www.gamedev.ru/code/tip/Collision_material мы разобрали как написать простой скрипт, с помощью которого можно создать костюмный материал, но мы не разобрали, как его использовать. В данной статье мы разберём как создать скрипт для экспорта из 3DS MAX геометрии коллизии с наложенным материалом.
В MAX Script Help предлагаю ознакомиться со следующими материалами:
<name> — используется, чтобы называть автоматически построенную локальную переменную, которая будет содержать значение, представляющее элемент управления, и используется для связывания функций обработчика событий с элементом управления.
<label_string> — используется в качестве заголовка, метки элемента или текстового содержимого, в зависимости от типа элемента управления, как описано в разделах для каждого типа.
<parameters> — последовательность аргументов ключевого слова, используемых для установки параметров или влияния макета для элемента управления. Точные параметры, поддерживаемые каждым типом элемента управления, также определяются в разделах для каждого типа.
Теперь нам предстоит создать функционал, который будет производить запись геометрии в файл формата XML. Дальше я не буду описывать всё в деталях и мелочах, так как это уже синтаксис cамого скриптового языка, буду добавлять некие замечания, объяснения, где это необходимо.
Для начала создадим новый метод “fn exportCollision =”. Его задачей будет получить имя файла, в который мы будем производить запись, получить 3д объект коллизии, который мы будем записывать, выставить трансформацию для объекта и, конечно, вызвать метод, который и произведёт запись.
fn exportCollision =
(
clearListener()
local fileName = (getSaveFileName types:"Static Collision|*.xml")
local collObj = selection[1]
if collObj != undefined then
(
if fileName != undefined then
(
local colTransform = collObj.transform
collObj.transform = Matrix3 1
createXMLFile fileName collObj
collObj.transform = colTransform
)
else
(
messagebox "Bad file name." title:"Information"
)
)
else
(
messagebox "Nothing to export! You haven't selected any object!" title:"Information"
)
)
Пояснения:
local collObj = selection[1] — тут происходит захват первого объекта который выделен во воюпорте и запись его в локальную переменную.
local colTransform = collObj.transform — записываем трансформацию объекта во временную переменную
collObj.transform = Matrix3 1 — назначаем нашему объекту единичную матрицу для того что бы он встал по центру вюпорта x = y = z = 0, убераем также любой поворот, а также маштаб.
createXMLFile fileName collObj — вызываем наш метод для записи меша колизии во файл.
collObj.transform = colTransform — возвращаем объект в изначальное положение.
Примечание: Не всегда статическую коллизию надо выставлять по центру. Перед экспортом в не которых физических движках более эффективно оставлять статику на своём оригинальном положении, как, например, с Bullet Physics.
Метод “createXMLFile”
fn createXMLFile fileName collisionObj =
(
print fileName
print collisionObj.name
local tmesh = Snapshotasmesh collisionObj
local materialCount = (getColMaterialCount tmesh) as integer
if doesFileExist(fileName) == false then
(
global XMLFile = (createFile fileName)
)
else
(
global XMLFile= (openFile fileName mode:"w+")
)
format "<?xml version=\"1.0\"?>" to:XMLFile
format "\n<CollisionShape>" to:XMLFile
format "\n\t<Name>%</Name>" collisionObj.name to:XMLFile
format "\n\n\t<Bounds>" to:XMLFile
format "\n\t\t<Min x=\"%\" y=\"%\" z=\"%\" />" \
collisionObj.min.x collisionObj.min.y collisionObj.min.z to:XMLFile
format "\n\t\t<Max x=\"%\" y=\"%\" z=\"%\" />" \
collisionObj.max.x collisionObj.max.y collisionObj.max.z to:XMLFile
format "\n\t</Bounds>" to:XMLFile
-------------------------------------Writing geometry data----------------------------------
---------------Writing vertices---------------
format "\n\n\t<VertexData count=\"%\" >" tmesh.numverts to:XMLFile
for v = 1 to tmesh.numverts do
(
local vert = (getvert tmesh v)
format "\n\t\t<Vertex x=\"%\" y=\"%\" z=\"%\" />" vert.x vert.y vert.z to:XMLFile
)
format "\n\t</VertexData>" to:XMLFile
---------------Writing faces-------------
format "\n\n\t<FaceData count=\"%\" materialCount=\"%\" >" tmesh.numfaces materialCount to:XMLFile
for f = 1 to tmesh.numfaces do
(
tface = (getface tmesh f)
faceMaterialID = getFaceMatId tmesh f
newMaterialID = (getReindexedMatID faceMaterialID)
format "\n\t\t<Face id1=\"%\" id2=\"%\" id3=\"%\" matID=\"%\" />" \
((tface.x - 1) as integer) ((tface.y - 1)as integer) ((tface.z - 1) as integer) \
newMaterialID to:XMLFile
)
format "\n\t</FaceData>" to:XMLFile
-------------------------------------Writing Materials-------------------------------------
format "\n\n\t<MaterialData count=\"%\" >" materialCount to:XMLFile
for m = 1 to reindexedMaterialIDS.count do
(
local mat = getMaterial collisionObj reindexedMaterialIDS[m].oldID
format "\n\t\t<Material matID=\"%\" matTypeID=\"%\" generateProps=\"%\" />" \
reindexedMaterialIDS[m].newID mat.M_MAT_TYPE mat.M_FLAG_PROPS to:XMLFile
)
format "\n\t</MaterialData>" to:XMLFile
format "\n\n</CollisionShape>" to:XMLFile
close XMLFile
delete tmesh
reindexedMaterialIDS = #()
messageBox "Done !"
)
Примечание: Если строка скрипта слишком длинна и вылезает за экран, это создаёт некие неудобства, поэтому её можно разделить на две или даже больше строк с помощью оператора "\".
глобальный массив который будет хранить ColMatID структуры.
fn getReindexedMatID oldMatID = (
local nID = 0
for m = 1 to reindexedMaterialIDS.count do
(
if oldMatID == reindexedMaterialIDS[m].oldID do
(
return reindexedMaterialIDS[m].newID
)
)
return nID
)
Один мультиматериал может быть наложен на несколько разных объектов в 3д максе. Этот же мультиматериал может содержать множество разных под-материалов, а это значит, что их индекс не всегда будет начинаться с нуля (единицы), и по порядку, как это будет записано в файле, поэтому нам нужно сделать некую переиндексацию, сохраняя старые ID материалов.
fn getColMaterialCount tmesh = (
local materialsUsed = #()
for f = 1 to tmesh.numfaces do (
mat_id = getFaceMatId tmesh f
appendIfUnique materialsUsed mat_id
)
for m = 1 to materialsUsed.count do (
local nID = ColMatID()
nID.oldID = materialsUsed[m] as integer
nID.newID = ((m as integer) - 1)
append reindexedMaterialIDS nID
)
return materialsUsed.count
)
В первом цикле мы собираем информацию и пополняем массив с уникальными индефикаторами материалов. Во втором цикле мы делаем переиндексацию и пополняем глобальный массив reindexedMaterialIDS структурами ColMatID.
Данный метод принимает изначальный объект, а также начальный ID (поскольку списки материала не изменились). Дальше идёт switch case с тремя возможностями. Если материал является мультиматериалом, то следовательно будут и под-материалы (должны быть). Если материал является TUT_COL_MAT, то мы его просто возвращаем, если нет — создаём такой и назначаем некие начальные значения.
Полный скрипт:
struct ColMatID (
oldID,
newID
)
global reindexedMaterialIDS = #()
fn getReindexedMatID oldMatID = (
local nID = 0
for m = 1 to reindexedMaterialIDS.count do
(
if oldMatID == reindexedMaterialIDS[m].oldID do
(
return reindexedMaterialIDS[m].newID
)
)
return nID
)
fn getColMaterialCount tmesh = (
local materialsUsed = #()
for f = 1 to tmesh.numfaces do (
mat_id = getFaceMatId tmesh f
appendIfUnique materialsUsed mat_id
)
for m = 1 to materialsUsed.count do (
local nID = ColMatID()
nID.oldID = materialsUsed[m] as integer
nID.newID = ((m as integer) - 1)
append reindexedMaterialIDS nID
)
return materialsUsed.count
)
fn getMaterial collObj oldMatID =
(
case classof collObj.mat of
(
Multimaterial:
(
if oldMatID >= 0 and oldMatID <= collObj.mat.count then
(
return collObj.mat[oldMatID]
)
else
(
tempMat = TUT_COL_MAT()
tempMat.M_MAT_TYPE = 1
tempMat.M_FLAG_PROPS = false
return tempMat
)
)
TUT_COL_MAT:
(
return collObj.mat
)
Default:
(
tempMat = TUT_COL_MAT()
tempMat.M_MAT_TYPE = 1
tempMat.M_FLAG_PROPS = false
return tempMat
)
)
)
fn createXMLFile fileName collisionObj =
(
print fileName
print collisionObj.name
local tmesh = Snapshotasmesh collisionObj
local materialCount = (getColMaterialCount tmesh) as integer
if doesFileExist(fileName) == false then
(
global XMLFile = (createFile fileName)
)
else
(
global XMLFile= (openFile fileName mode:"w+")
)
format "<?xml version=\"1.0\"?>" to:XMLFile
format "\n<CollisionShape>" to:XMLFile
format "\n\t<Name>%</Name>" collisionObj.name to:XMLFile
format "\n\n\t<Bounds>" to:XMLFile
format "\n\t\t<Min x=\"%\" y=\"%\" z=\"%\" />" \
collisionObj.min.x collisionObj.min.y collisionObj.min.z to:XMLFile
format "\n\t\t<Max x=\"%\" y=\"%\" z=\"%\" />" \
collisionObj.max.x collisionObj.max.y collisionObj.max.z to:XMLFile
format "\n\t</Bounds>" to:XMLFile
-------------------------------------Writing geometry data----------------------------------
---------------Writing vertices---------------
format "\n\n\t<VertexData count=\"%\" >" tmesh.numverts to:XMLFile
for v = 1 to tmesh.numverts do
(
local vert = (getvert tmesh v)
format "\n\t\t<Vertex x=\"%\" y=\"%\" z=\"%\" />" vert.x vert.y vert.z to:XMLFile
)
format "\n\t</VertexData>" to:XMLFile
---------------Writing faces-------------
format "\n\n\t<FaceData count=\"%\" materialCount=\"%\" >" tmesh.numfaces materialCount to:XMLFile
for f = 1 to tmesh.numfaces do
(
tface = (getface tmesh f)
faceMaterialID = getFaceMatId tmesh f
newMaterialID = (getReindexedMatID faceMaterialID)
format "\n\t\t<Face id1=\"%\" id2=\"%\" id3=\"%\" matID=\"%\" />" \
((tface.x - 1) as integer) ((tface.y - 1)as integer) ((tface.z - 1) as integer) \
newMaterialID to:XMLFile
)
format "\n\t</FaceData>" to:XMLFile
-------------------------------------Writing Materials-------------------------------------
format "\n\n\t<MaterialData count=\"%\" >" materialCount to:XMLFile
for m = 1 to reindexedMaterialIDS.count do
(
local mat = getMaterial collisionObj reindexedMaterialIDS[m].oldID
format "\n\t\t<Material matID=\"%\" matTypeID=\"%\" generateProps=\"%\" />" \
reindexedMaterialIDS[m].newID mat.M_MAT_TYPE mat.M_FLAG_PROPS to:XMLFile
)
format "\n\t</MaterialData>" to:XMLFile
format "\n\n</CollisionShape>" to:XMLFile
close XMLFile
delete tmesh
reindexedMaterialIDS = #()
messageBox "Done !"
)
fn exportCollision =
(
clearListener()
local fileName = (getSaveFileName types:"Static Collision|*.xml")
local collObj = selection[1]
if collObj != undefined then
(
if fileName != undefined then
(
local colTransform = collObj.transform
collObj.transform = Matrix3 1
createXMLFile fileName collObj
collObj.transform = colTransform
)
else
(
messagebox "Bad file name." title:"Information"
)
)
else
(
messagebox "Nothing to export! You haven't selected any object!" title:"Information"
)
)
rollout exportCollisionRollout "Collision Exporter" width:175 height:83
(
button 'btn1' "Export Static Collision" pos:[27,21] width:120 height:40 align:#left
on btn1 pressed do
(
exportCollision()
)
)
createDialog exportCollisionRollout
Здесь есть еще место для дальнейшего развития, создание специального плагина для чтения и записи специфических форматов используя с++ и пополняя макс-скрипт функционалом, а также экспорт 3д графических моделей для рендера, в скором времени я пополню нашу библиотеку данными темами.