-- TPM Exporter
--  Version 1.2.5
--  by Martijn Buijs, Andres James
--  Copyright © TresCom, 2007

-- Version History:
-- 1.2.5
--  * removed messagebox displaying the name of un-exportable objects
--  * cleaned up code
--  * added file footer comment, makes it easier to see if file is currupt
-- 1.2.4
--  * added workaround for "getfacernormals" bug in 3dsmax 4 (thanks to Machf)
-- 1.2.3
--  * tool now sits in a floating tool window
--  * script no longer crashes if file is not accessable
--  * smoothing groups are now properly exported
--  * fix scale option now actually works
--  * pivot offset is now properly handled (but not pivot rotation)
-- 1.2.2
--  * added export "Selection Only" option
--  * added several warnings
--  * fixed crash on hidden submaterials
--  * fixed several stability issues related to materials
--  * only materials of exported meshes are written to file
--  * non standard materials are now ignored
--  * disabled (unchecked) material maps are no longer exported
--  * cleaned up code and added comments

-- To do:
--  * create flipped triangles copies for twosided materials (optional)
--  * export mirrored objects correctly

-- clean up
clearlistener()
global TpmExp
if TpmExp != undefined do
(
 closeRolloutFloater TpmExp
)

-- file handle
global filename
global fileobj

-- options
global opt_scalefix = false

-- material redundancy list
global matlist = #()
global matlistnum = 0

-- warnings
global warn_notobj = false
global warn_mirror = false
global warn_twoside = false

-- write material
fn WriteMaterial mat =
(
 -- check if material is already exported
 for i=1 to matlistnum do
 (
  if matlist[i] == mat do return false
 )

 -- write material
 format "\nmaterial \"%\"\n{\n" mat.name to: fileobj
 if (classOf(mat.diffusemap) == BitmapTexture) AND (mat.diffusemapenable == true) do
 (
  format " colormap = \"%\"\n" mat.diffusemap.filename to: fileobj
 )
 if (classOf(mat.opacitymap) == BitmapTexture) AND (mat.opacitymapenable == true) do
 (
  format " opacitymap = \"%\"\n" mat.opacitymap.filename to: fileobj
 )
 format "}\n" to: fileobj
 
 -- add to redundancy list
 matlistnum = matlistnum+1
 matlist[matlistnum] = mat
 
 -- warnings
 if mat.twosided do warn_twoside = true
)

-- write bones
fn WriteBoneChildren obj =
(
 local i
 if obj.children.count > 0 do
 (
  for i=1 to obj.children.count do
  (
   format " j = \"%\"" obj.children[i].name to: fileobj
   format ",(%,%,%)" obj.children[i].pos.x obj.children[i].pos.y obj.children[i].pos.z to: fileobj
   format ",(-90,-90,-90),(90,90,90),\"%\"\n" obj.name to: fileobj
   WriteBoneChildren obj.children[i]
  )
 )
)

-- write bonesystem
fn WriteBonesys obj =
(
 if obj.parent == undefined do
 (
  format "\nbonesys \"%\"\n{\n" obj.name to: fileobj
  format " r = \"%\",(%,%,%)\n" obj.name obj.pos.x obj.pos.y obj.pos.z to: fileobj
  WriteBoneChildren obj
  format "}\n" to: fileobj
 )
)


-- adds float3 to list
fn addToList list v =
(
 if (v == undefined) do return list.count --bugfix
 
 for i=1 to list.count do
 (
  if list[i].x == v.x do (
   if list[i].y == v.y do (
    if list[i].z == v.z do (
     return i
    )
   )
  )
 )
 append list v
 return list.count
)


-- write mesh
fn WriteMesh obj =
(
 local op = obj.objectoffsetpos
 local os = obj.scale
 
 -- write mesh materials
 case classof(obj.mat) of
 (
  UndefinedClass:()
  StandardMaterial:
  (
   WriteMaterial obj.mat
  )
  MultiMaterial:
  (
   for i=1 to obj.mat.numsubs do
   (
    if classof(obj.mat[i]) == StandardMaterial do
    (
     WriteMaterial obj.mat[i]
    )
   )
  )
 )
 
 local isskin = false
 local boneVerts = #()
 local i
 local k
 if classof(obj.modifiers[1]) == Skin then
 (
  isskin = true
  local myskin = obj.modifiers[1]
  
  -- skin stuff only works if the modifer panel is open and mesh is selected
  max modify mode
  select obj
  
  -- collect up bone indicies for each vertex
  for i=1 to skinOps.getNumberVertices myskin do
  (
   -- Trespasser only supports singly weighted verticies, so choose bone with greatest influence on vertex
   local maxb = 0 -- best bone so far
   local maxw = 0.0 -- best weight so far
   for k=1 to (skinops.getVertexWeightCount myskin i) do
   (
    if (skinops.getVertexWeight myskin i k) > maxw then
    (
     maxw = (skinops.getVertexWeight myskin i k)
     maxb = (skinops.getVertexWeightBoneID myskin i k)
    )
    append boneVerts maxb -- TODO: should be error if not attached to any bone?
   )
  )
  format "\nskin \"%\"\n{\n" obj.name to: fileobj
 ) else (
  format "\nmesh \"%\"\n{\n" obj.name to: fileobj
 )
 
 -- standard material
 case classof(obj.mat) of
 (
  StandardMaterial:
  (
   format " m = \"%\"\n" obj.mat.name to: fileobj
  )
  MultiMaterial:
  (
   for i=1 to obj.mat.numsubs do
   (
    if classof(obj.mat[i]) == StandardMaterial then
    (
     format " m = \"%\"\n" obj.mat[i].name to: fileobj
    ) else (
     format " m = \"dummy\"\n" to: fileobj
    )
   )
  )
 )
 
 -- write vertices
 local v
 for i=1 to obj.numverts do
 (
  in coordsys local v = (getvert obj i)
  
  if opt_scalefix do
  (
   v = (v + op) * os
  )
  
  if isskin == true then
  (
   format " v = (%,%,%),%\n" v.x v.y v.z (boneVerts[i]-1) to: fileobj -- write extra bone index
  ) else (
   format " v = (%,%,%)\n" v.x v.y v.z to: fileobj
  )
 )
 
 -- write texcoords
 local t
 if obj.numtverts == 0 then
 (
  format " t = (0,0)\n" to: fileobj
 ) else (
  for i=1 to obj.numtverts do
  (
   t = (gettvert obj i)
   format " t = (%,%)\n" t.x t.y to: fileobj
  )
 )
 
 -- write normals
 local normlist = #()
 local fnlist = #()
 for i=1 to obj.numfaces do
 (
  if (getfacesmoothgroup obj i) == 0 then
  (
   in coordsys local frn = getfacenormal obj i
   n1 = addToList normlist frn
   n2 = n1
   n3 = n1
  ) else (
   in coordsys local frn = (meshop.getfacernormals obj i)
   
   -- start bug workaround: sometimes frn does not have three elements in 3dsmax 4 (mxs bug)
   if frn.count != 3 do 
   (
    in coordsys local frn[1] = getfacenormal obj i
	frn[2] = frn[1]
	frn[3] = frn[1]
   )
   -- end bug workaround
   
   n1 = addToList normlist frn[1]
   n2 = addToList normlist frn[2]
   n3 = addToList normlist frn[3] 
  )
  append fnlist #(n1,n2,n3)
 )
 for i=1 to normlist.count do
 (
  format " n = (%,%,%)\n" normlist[i].x normlist[i].y normlist[i].z to: fileobj
 )
 normlist = #()
 
 -- write faces
 for i=1 to obj.numfaces do
 (
 
  -- face vertex pointers
  fv = (getface obj i)
  format " f = (%,%,%)" (fv[1] as integer) (fv[2] as integer) (fv[3] as integer) to: fileobj
  
  -- face texcoord indices
  if obj.numtverts == 0 then
  (
   format ",(1,1,1)" to: fileobj -- no texcoords, so lets point texcoord indices to this dummy coord
  ) else (
   ft = (gettvface obj i)
   format ",(%,%,%)" (ft[1] as integer) (ft[2] as integer) (ft[3] as integer) to: fileobj
  )
  
  -- face normal indices
  format ",(%,%,%)" (fnlist[i][1] as integer) (fnlist[i][2] as integer) (fnlist[i][3] as integer) to: fileobj
  
  -- material indices
  case classof(obj.material) of
  (
   UndefinedClass:
   (
    -- no material on mesh
    format ",0\n" to: fileobj
   )
   StandardMaterial:
   (
    -- single material for entire mesh
    format ",1\n" to: fileobj
   )
   MultiMaterial:
   (
    -- submaterials, per face
    format ",%\n" (getfacematid obj i as integer) to: fileobj
   )
  )
 )
 
 -- clean up
 fnlist = #()
 
 -- end of mesh chunk
 format "}\n" to: fileobj
)

-- write instance
fn WriteInstance obj =
(
 format "\ninstance \"%\"\n{\n" obj.name to: fileobj
 
 -- mesh binding
 format " mesh = \"%\"\n" obj.name to: fileobj
 
 -- position
 format " position = (%,%,%)\n" obj.pos.x obj.pos.y obj.pos.z to: fileobj
 
 -- rotation
 local ea = quatToEuler obj.rotation order:1 
 -- TODO: check which order is correct
 -- "euler angles seem to be -ve what they should be, probably stuffing up the rotation order too, should check"
 --  -Andres
 format " rotation = (%,%,%)\n" -ea.x -ea.y -ea.z to: fileobj
 
 -- scale
 if opt_scalefix then
 (
  format " scale = 1\n" to: fileobj
 ) else (
  format " scale = %\n" obj.scale.x to: fileobj
 )
 
 -- end of instance chunk
 format "}\n" to: fileobj
)

rollout rExport "TPM Exporter"
(
 group "Options"
 (
  checkbox chkScaleFix "Fix Scale" checked: true
 )
 group "Export"
 (
  checkbox chkSelected "Selection Only" checked: true
  checkbox chkSkipHidden "Skip Hidden Objects" checked: true
  checkbox chkSkipFrozen "Skip Frozen Objects" checked: true
  button cmdExport "Export Scene..." width: 140
 )
 on cmdExport pressed do
 (
  local j
  local b
  
  -- options
  opt_scalefix = chkScaleFix.checked
  
  -- reset material redundancy list
  matlist = #()
  matlistnum = 0
  
  -- reset warning flags
  warn_notobj = false
  warn_mirror = false
  warn_twoside = false
  
  -- save file dialog
  filename = getSaveFileName caption:"Export Scene" types:"Trespasser Models (*.tpm)|*.tpm|All Files (*.*)|*.*"
  if filename == undefined then
  (
   return false
  )
  
  fileobj = createfile filename
  if fileobj == undefined do
  (
   messagebox "Could not create file!"
   return false
  )
   
  -- print file header
  format "// Trespasser Model File\n" to: fileobj
  format "\nfileinfo\n{\n" to: fileobj
  format " formatversion = 1.0.1\n" to: fileobj
  format " source = \"%\"\n" maxFileName to: fileobj
  format " date = %\n" localTime to: fileobj
  format "}\n" to: fileobj
   
  -- write objects
  local obj
  for j=1 to $objects.count do
  (
   obj = $objects[j]
  
   -- skip hidden
   if chkSkipHidden.checked == true do
   (
    if obj.ishidden do continue
   )
   
   -- skip frozen
   if chkSkipFrozen.checked == true do
   (
    if obj.isFrozen do continue
   )
   
   -- skip unselected
   if chkSelected.checked == true do
   (
    if not obj.isSelected do continue
   )
   
   -- write data block
   case classof(obj) of
   (
    
    -- print bonesys (max 2.5)
    Bone:
    (
     WriteBonesys obj
    )
   
    -- write bonesys (max 3.0 and up)
    BoneGeometry:
    (
     format "\nbone \"%\"\n{\n" obj.name to: fileobj
     format " position = (%,%,%)\n" obj.pos.x obj.pos.y obj.pos.z to: fileobj
     local userProps = getuserpropbuffer obj
     if userProps != undefined AND userProps != "" do
     (
      format "%" userProps to: fileobj
     )
     format"}\n" to: fileobj
    )
    
    -- write mesh
    Editable_mesh:
    (
     WriteMesh obj
     WriteInstance obj
    )
    
    -- silently ignore
    OmniLight:()
    FreeSpot:()
    TargetSpot:()
    DirectionalLight:()
    TargetDirectionalLight:()
     
    -- other objects
    default:
    (
     warn_notobj = true
     --messagebox obj.name
    )
    
   )
   
   -- warnings
   if obj.scale.x < 0 do warn_mirror = true
   if obj.scale.y < 0 do warn_mirror = true
   if obj.scale.z < 0 do warn_mirror = true
  )
  
  -- write footer
  format "\n// end of file" to: fileobj
  
  -- close file
  close fileobj
  
  -- clean up material redundancy list
  matlist = #()
  matlistnum = 0
  
  -- display warnings
  local errstr = ""
  if warn_notobj do  errstr = errstr + "* not all scene objects could be exported\n"
  if warn_mirror do  errstr = errstr + "* some objects are mirrored, this may lead to object orientation problems\n"
  if warn_twoside do errstr = errstr + "* some materials are two sided, these are not preserved\n"
  if errstr.count > 0 do messagebox ("Warning:\n" + errstr)
   
  -- success confirmation
  messagebox "Scene exported successfully."
  
 )
)

rollout rAbout "About"
(
 label lab1 "TPM Exporter"
 label lab2 "Version 1.2.5"
 label lab3 "by Martijn Buijs, Andres James"
 label lab4 "Copyright © TresCom, 2007"
)

-- floater
TpmExp = newRolloutFloater "TpmExp" 180 300
addRollout rExport TpmExp
addRollout rAbout TpmExp

-- END OF FILE


