-- TPM Exporter
-- Version 1.2.2
-- by Martijn Buijs, Andres James
-- Copyright © TresCom, 2006

-- Version History:
-- 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 (optional)
-- * try to use "render normals" to export smoothing groups properly

-- file handle
global filename
global fileobj

-- options
global opt_fixscale = 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
 )
)

-- write mesh
fn WriteMesh obj =
(
 
 -- 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
  (
   local maxb = 0 -- best bone so far
   local maxw = 0.0 -- best weight so far
   -- Trespasser only supports singly weighted verticies, so choose bone with greatest influence on vertex
   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_fixscale do
  (
   v.x = v.x * (1/s.x)
   v.y = v.y * (1/s.y)
   v.z = v.z * (1/s.z)
  )
  
  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
 (
  for i=1 to obj.numtverts do
  (
   t = (gettvert obj i)
   format " t = (%,%)\n" t.x t.y to: fileobj
  )
 ) else (
  format " t = (0,0)\n" to: fileobj
 )
 
 -- write normals
 local n
 for i=1 to obj.numverts do
 (
  in coordsys local n = (getnormal obj i)
  format " n = (%,%,%)\n" n.x n.y n.z to: fileobj
 )
 
 -- write faces
 for i=1 to obj.numfaces do
 (
 
  -- face vertex pointers
  v = (getface obj i)
  if obj.numtverts != 0 do
  (
   t = (gettvface obj i)
  )
  n = (getface obj i)
  f = (getfacematid obj i)
  format " f = (%,%,%)" (v.x as integer) (v.y as integer) (v.z as integer) to: fileobj
  
  -- face texcoord indices
  if not obj.numtverts == 0 then
  (
   format ",(%,%,%)" (t.x as integer) (t.y as integer) (t.z as integer) to: fileobj
  ) else (
   -- let dummy texcoord indices point to this
   format ",(1,1,1)" to: fileobj
  )
  
  -- face normal indices
  format ",(%,%,%)" (n.x as integer) (n.y as integer) (n.z 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
   )
  )
 )
 
 -- 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
 format " rotation = (%,%,%)\n" -ea.x -ea.y -ea.z to: fileobj
 
 -- scale
 if opt_fixscale then
 (
  format " scale = 1\n" to: fileobj
 ) else (
  format " scale = %\n" obj.scale.x to: fileobj
 )
 
 -- end of instance chunk
 format "}\n" to: fileobj
)

utility TpmExp "TPM Exporter"
(
 group "About"
 (
  label lab1 "TPM Exporter"
  label lab2 "Version 1.2.2"
  label lab3 "by Martijn Buijs, Andres James"
  label lab4 "Copyright © TresCom, 2006"
 )
 group "Options"
 (
  checkbox chkScaleFix "Fix Scale" checked: true
 )
 group "Export"
 (
  checkbox chkSelected "Selection Only" checked: false
  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
  (
   fileobj = createfile filename
   
   -- print file header
   format "// Trespasser Model File (.tpm)\n" to: fileobj
   format "\nfileinfo\n{\n" to: fileobj
   format " formatversion = 1.0.1\n" to: fileobj
   format " name = \"%\"\n" maxFileName to: fileobj
   format " version = 1.2.2\n" to: fileobj
   format " source = \"%\"\n" maxFileName to: fileobj
   format " date = %\n" localTime to: fileobj
   format " comments = \"\"\n" 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
   )
   
   -- close file
   close fileobj
   
   -- clean up material redundancy list
   matlist = #()
   matlistnum = 0
   
   -- 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."
   
  )
 )
)

