-- TPMImport script.
-- This is a horrible mish-mash of code from lots of places
-- Plain mesh import should work fine
-- There are lots of problems when creating MAX Skins/Joints/Bones

-- Known problems:
-- - Rotation order not checked
-- - Scaling mesh instance stuffs up the skin
-- - Skin ends up with many extra weights that I don't assign, automatically added when bones created?
--   (they seem to always be zero though, so not too bad)
-- Pushing the Skin.always_deform button twice nicely resets the reference position of the verts,
-- but setting it in a script doesn't! ARGHHHHHHHHHHH!!!

utility ImportTPM "TPM Import"
(
	local currentPath
	
	--------------------
	-- User Interface --
	--------------------
	group "About"
	(
		label lab1 "TPM Import v0.1 alpha"
		hyperLink addy "by Andres James" align:#center address:"mailto:tresutils@yahoo.com" color:(color 0 0 170) hoverColor:(color 170 0 0)

		button dlgTPMImportHelp "HELP"
	)

	on dlgTPMImportHelp pressed do (
		local helpStr = "
TPM Import lets you import Trespasser Model (.TPM) files, for example those exported by TresEd.

Usage:
Hit the \"Import TPM\" button and select the file to import. If you have any problems,
or if you intend to make extensive use of this script, please completely read this
dialog and the included readme.txt file.

Tips and Caveats:
- All meshes exported by TresEd should work.
- In theory a dino can be imported, edited, exported, and imported back into
  Trespasser with GeomAdd. I've tested changing vertex and tvert positions, but
  I haven't tried modifying which bone a vertex is attached to.
- Bumpmaps have not been tested
- For 'skin' types, a Skin modifier is added to the mesh, using bones named
  '$J%NN' where % is the mesh name, and NN is the bone number.
  NOTE: Following the Trespasser convention, bone numbers start at zero, as
  do the bone indicies in the vertex definitions.
  
IMPORTANT NOTES ABOUT IMPORTING JOINTS/BONES
- You must not scale the imported object (ie. use Import Scale = 1.0)
- Before the skin modifier (ie. joints/bones) will correctly act on a mesh you must
  do the following: Open the Modifier panel and select the \"Skin\" modifier. In the
  \"Advance Params\" rollout deselect \"Always Deform\" then select it again.
- Some bone parameters are stored in the User Properties text buffer and can
  only be modified there. eg. Anim00, Anim01, Ratio, and RotationRatio.
- Bone hierarchy is not set up ie. bones have no parents/children.
- Loading a mesh with bones can be MUCH slower than loading a plain mesh, be patient.
"
		messageBox helpStr title:"TPM Import Help"
	)
	
	local gImportScale = 10.0
		
	group "Import"
	(
		checkbox resetCheck "Reset MAX scene" checked:false disabled:true
		spinner spnImportScale "Import scale" range:[0.0000001,100,gImportScale]
		button importTPMButton "Import TPM..." toolTip:""
		--button testImportTPMButton "My test fn..." toolTip:""
	)

	on spnImportScale changed val do (
		gImportScale = val
	)

	--------------------
	-- Misc Functions --
	--------------------

fn IsInstance objA objB = (
	(isKindOf objA node) AND
	(isKindOf objB node) AND
	(objA.baseObject == objB.baseObject)
)

fn GetInstances obj = (
	local objs

	local bObj = obj.baseObject
	local rObjs = refs.dependents bObj
	objs = for rObj in rObjs where ( (isKindOf rObj node) AND
									(bObj == rObj.baseObject) AND
									(obj != rObj) ) collect rObj
	return objs
)
	--------------------------
	-- TPM Import Functions --
	--------------------------

	-- Load a texture file
	fn GetTexture name = (
		local bmpFilename = name
		
		local texMap = sceneMaterials[bmpFilename]
		if texMap != undefined then
		(
			if (classof texMap) != BitmapTexture then
			(
				texMap = undefined
			) else
			(
				return texMap
			)
		)
		
		try
		(
			--print currentpath
			--print bmpfilename
			local bmp = openBitMap (currentPath + bmpFilename)
			texMap = BitmapTexture bitmap:bmp name:bmpFileName
		) catch
		(
			return undefined
		)
		texMap
	)
	
	-- Set parent of bone b to be bone p
	fn SetBoneParent b p = (
		-- Want to re-align bone so finishes at parent, not working...
		--p.dir = b.pos - p.pos
		--p.length = 20 --distance p.pos b.pos
		skinops.setendpoint
		b.parent = p
	)
	fn SetBoneParent2 ms b p  = (
		-- Why won't this work!!!!
		local pos = skinops.getStartPoint ms (p as integer)
		skinops.setendpoint ms (b as integer) pos
		--b.parent = p
	)


	-- given mesh, array of bones for mesh, and bone number for each vertex, setup bones and a Skin modifier
	-- bone numbers and boneVerts start at '1'
	fn SetupBones m bones boneVerts = (
		-- Skin stuff only works if the modifer panel is open, and mesh is selected
		max modify mode
		select m
		ms = Skin()
		ms.rigid_vertices = true
		addModifier m ms
		
		-- Hack to try mark bone parents, should be in seperate script as they're different for each dino type
		local gpar = #( [4,3],[3,2],[2,1],[1,0], [15,14],[14,13],[13,12], [19,18],[18,17],[17,16],[14,16], [7,6],[8,7],[9,7],[23,7],[26,7])
		--for i=1 to gpar.count do
			--SetBoneParent bones[gpar[i][1]+1] bones[gpar[i][2]+1]

		-- Add bones to mesh, using update flag '-1' for last bone
		for i=1 to bones.count-1 do
			skinops.addbone ms bones[i] 0
		if bones.count > 0 then
			skinops.addbone ms bones[bones.count] -1
		update m -- do we need this?

		--for i=1 to gpar.count do
			--SetBoneParent2 ms (gpar[i][1]+1) (gpar[i][2]+1)
		
		-- Add vertex weights
		for i=1 to m.verts.count do (
			skinops.setVertexWeights ms i boneverts[i] 1
		)
		update m -- do we need this?
	)
	
	-- Return list of bones for given mesh, searching through the given object list
	-- Bone name is of form "$J%NN" Where % is the mesh name, and NN is a number
	-- If zeroIsValid is false, bone number 'k' is returned in list position 'k' as expected.
	-- If zeroIsValid is true, then 00 is a valid bone number, and bone number 'k' is
	-- returned in position 'k+1' in the list (because MAX lists can only start at 1...)
	fn FindBones m objs zeroIsValid:false = (
		-- Find bones for given mesh, 
		format "FindBones\n"
		local tempBones = #()
		local startIndex = 1
		if zeroIsValid == true then startIndex = 0
		for j=startIndex to 40 do (
			local n = (j as string)
			if j < 10 then
				n = "0" + (j as string)
			local boneName = "$J" + m.name + n
			local b = (for b in objs where b.name == boneName collect b)[1]
			if b != undefined then (
				tempBones[j+1-startIndex] = b
				format "Found %\n" b.name
			)
		)
		return tempBones
	)
	
	fn ImportTPM O_File = (
		
		if O_File == undefined then
		(
			print "File not found"
			return false
		)
		format "Importing '%'...\n" O_File
		currentPath = getFilenamePath O_File
			
		I_Stream = openFile O_File mode:"r"

		local verts = #()
		local tverts = #()
		local faces = #()
		local tvfaces = #()
		local boneverts = #()
		local faceMats = #()

		local meshName = "DefaultMeshName"
		local meshMats = #()
		local usedMeshes = #() -- keep track of which meshes we've created instances of already
		
		local instName
		local instPos, instRot, instScale
		
		local matName = undefined
		local matDiffuseMap = undefined
		local matOpacityMap = undefined
		local matBumpMap = undefined
		local materials = #()
		local materialNames = #()
		
		local bones = #()
		local bonePosition
		local boneName
		
		local userProps = ""
		
		local parseMode = "root"
		local unknownMode = false
		local doneSkinWarning = false
		
		while ((eof I_Stream) == false) do
		(
			Cur_Line = readLine I_Stream
			stringArray = filterString Cur_Line " =()<>,\""
			--LineToken = read_token Cur_Line
			LineToken = stringArray[1]

			-- Skip empty lines and comments
			if (LineToken == undefined) then continue
			if (LineToken == "") then continue
			if (substring LineToken 1 2 == "//") then continue

			if unknownMode == true then ( -- Skip interior of unknown blocks
				if LineToken == "}" then (
					unknownMode = false
				)
			) else
			if parseMode == "root" then (
				userProps = ""
				case LineToken of
				(
					"fileinfo": (
						unknownMode = true
					)
					"bone": (
						parseMode = "bone"
						boneName = stringArray[2]
						bonePosition = [0,0,0]
					)
					"j": ( --@HACK, TresEd only supports bones (joints) in old format
						boneName = stringArray[2]
						x = (stringArray[3] as float)
						y = (stringArray[4] as float)
						z = (stringArray[5] as float)
						local size = gImportScale * 0.1
						local bone = BoneSys.createBone [0,0,0] [size,0,0] [0,0,1];
						bone.name = boneName
						bone.width = size
						bone.height = size
						bone.pos = [x, y, z] * gImportScale
						append bones bone
						format "bone % % % %\n" boneName x y z
					)
					"material": (
						parseMode = "material"
						matName = stringArray[2]
						matDiffuseMap = undefined
						matOpacityMap = undefined
						matBumpMap = undefined
					)
					"mesh": (
						parseMode = "mesh"
						meshName = stringArray[2]
						verts = #()
						faces = #()
						tverts = #()
						tvfaces = #()
						faceMats = #()
						meshMats = #()
						boneverts = #()
					)
					"skin": (
						parseMode = "skin"
						meshName = stringArray[2]
						verts = #()
						faces = #()
						tverts = #()
						tvfaces = #()
						faceMats = #()
						meshMats = #()
						boneverts = #()
					)
					"instance": (
						parseMode = "instance"
						instName = stringArray[2]
						meshName = instName
						instPos = [0,0,0]
						instRot = [0,0,0]
						instScale = [1,1,1]
					)
					"{": ()
					"}":(
						unknownMode = false
					)
					default: (
						if unknownMode == false then (
							format "Unknown root element %\n" LineToken 
						)
						unknownMode = true
					)
				)
			) else
			if parseMode == "bone" then ( -- untested
				case LineToken of
				(
					"position": (
						x = (stringArray[2] as float)
						y = (stringArray[3] as float)
						z = (stringArray[4] as float)
						bonePosition = [x, y, z] * gImportScale
						--format "bonepos % % %\n" x y z
					)
					"}": (
						parseMode = "root"
						local size = gImportScale * 0.1
						local bone = BoneSys.createBone [0,0,0] [size,0,0] [0,0,1];
						bone.name = boneName
						bone.width = size
						bone.height = size
						bone.pos = [x, y, z] * gImportScale
						setuserpropbuffer bone userProps
						append bones bone
						format "bone % % % %\n" boneName x y z
					)
					"Anim00": ( userProps += Cur_Line + "\n" )
					"Anim01": ( userProps += Cur_Line + "\n" )
					"Ratio": ( userProps += Cur_Line + "\n" )
					"RotationRatio": ( userProps += Cur_Line + "\n" )
					"rotation": ( userProps += Cur_Line + "\n" )
				)
			) else
			if parseMode == "material" then (
				case LineToken of
				(
					"colormap": (
						matDiffuseMap = fileNameFromPath stringArray[2]
						--format "diffusemap %\n" matDiffuseMap
					)
					"opacitymap": (
						matOpacityMap = fileNameFromPath stringArray[2]
					)
					"bumpmap": (
						matBumpMap = fileNameFromPath stringArray[2]
					)
					"}": (
						parseMode = "root"
						local mat =  standard name:matName showInViewport:true
						if matDiffuseMap != undefined then (
							local texmap = GetTexture matDiffuseMap
							if texmap != undefined then (
								mat.maps[2] = texmap
								mat.mapEnables[2] = true
								showTextureMap mat texmap true
							) else
								format "Warning: Can't find texture map file %\n" matDiffuseMap
						)
						if matOpacityMap != undefined then (
							local texmap = GetTexture matOpacityMap
							if texmap != undefined then (
								mat.maps[7] = texmap
								mat.mapEnables[7] = true
								showTextureMap mat texmap true
							) else
								format "Warning: Can't find opacity map fimle %\n" matOpacityMap
						)
						if matBumpMap != undefined then (
							local texmap = GetTexture matBumpMap
							if texmap != undefined then (
								mat.maps[9] = texmap
								mat.mapEnables[9] = true
								showTextureMap mat texmap true
							) else
								format "Warning: Can't find bump map file %\n" matBumpMap
						)
						showTextureMap mat on
						append materialNames matName
						append materials mat
						format "material %\n" matName
					)
				)
			) else
			if parseMode == "mesh" or parseMode == "skin" then (
				case LineToken of
				(
					"m":
					(
						-- Find material in global list
						local index = findItem materialNames stringArray[2]
						if index != 0 then
						(
							--format "material %\n" stringArray[2]
							append meshMats materials[index]
						) else (
							format "material % not found, substituted default\n" stringArray[2]
							local tempmat = standard name:stringArray[2]
							append meshMats tempmat
						)
					)
					"v":
					(
						x = (stringArray[2] as float)
						y = (stringArray[3] as float)
						z = (stringArray[4] as float)
						append verts ([x, y, z] * gImportScale)
						local b = undefined
						if stringArray[5] != undefined then b = (stringArray[5] as integer)
						if (parseMode=="skin") then (
							if b == undefined then (
								format "missing bone index\n"
								b = 0
							)
							if b < 0 then (
								format "unknown bone index %\n" b
								b = 0
							)
							b = b + 1 -- because TPM bones and bone indicies start at zero
							append boneverts b
						)
						--format "vertex % % %\n" x y z
					)
					"t":
					(
						x = (stringArray[2] as float)
						y = (stringArray[3] as float)
						append tverts [x, y, 0]
						--format "tvert % %\n" x y
					)
					"f":
					(
						a = (stringArray[2] as integer)
						b = (stringArray[3] as integer)
						c = (stringArray[4] as integer)
						append faces [a, b, c]
						a2 = (stringArray[5] as integer)
						b2 = (stringArray[6] as integer)
						c2 = (stringArray[7] as integer)
						append tvfaces [a2, b2, c2]
						m = (stringArray[11] as integer)
						append faceMats m
						--format "face (% % %) (% % %) %\n" a b c a2 b2 c2 m
					)
					"}": (
						if parseMode == "skin" then
							format "skin %\n" meshName
						else
							format "mesh %\n" meshName
						--@TODO what if no meshMats?
						local material = multiMaterial numsubs:meshMats.count name:meshName
						for i = 1 to meshMats.count do (
							--format "mat: %\n" meshMats[i].name
							material[i] = meshMats[i]
						)
						-- Make an editable mesh
						local ss = 1
						--local ss = 2.47791 --@HACK for raptorC
						local m = mesh name:meshName position:[0, 0, 0] scale:[ss, ss, ss] \
							faces:faces vertices:verts material:material
						--update m
						setNumTVerts m tverts.count false
						for i = 1 to tverts.count do settvert m i tverts[i]
						buildTVFaces m false
						for i = 1 to tvfaces.count do settvface m i tvfaces[i]
						-- Assign material ID's NB. materials must be already defined
						for i = 1 to faces.count do
						(
							local matID = 1
							--local matSlot = 1
							--matID = material.materialIDList[matSlot]
							setFaceMatID m i faceMats[i]
						)
						update m
						
						-- Setup Bones
						if parseMode == "skin" then (
							if doneSkinWarning == false then (
								messageBox "NOTE: Importing skins can take a VERY long time" title:"TPM Skin import"
								doneSkinWarning = true
							)
							local meshBones = FindBones m bones zeroIsValid:true
							-- Make sure all bones are defined or SetupBones will complain
							if meshBones != undefined then (
								format "Found % bones\n" meshBones.count
								SetupBones m meshBones boneVerts
							)
						)
						parseMode = "root"
					)
				)
			) else
			if parseMode == "instance" then (
				case LineToken of
				(
					"position": (
						instPos = ([(stringArray[2] as float), (stringArray[3] as float), (stringArray[4] as float)] * gImportScale)
					)
					"rotation": (
						instRot = ([(stringArray[2] as float), (stringArray[3] as float), (stringArray[4] as float)])
					)
					"scale": (
						local s = (stringArray[2] as float)
						instScale = [s, s, s]
					)
					"mesh": (
						meshName = stringArray[2]
					)
					"}": (
						parseMode = "root"
						-- find mesh
						local bmesh = undefined
						for m in objects where m.name==meshName do bmesh = m
						if bmesh != undefined then (
							local doneBefore = false
							 -- save 'mesh' object so we can delete it later (as we now have at least one instance of it)
							for o in usedMeshes where o.name==meshName do
								doneBefore = true
							if doneBefore == false then
								append usedMeshes bmesh
							-- create new instance
							local inst = instance bmesh
							inst.name = instName
							inst.pos = instPos
							inst.rotation = quat 0 0 0 1
							inst.scale = instScale
							--local euler = eulerAngles instRot.x instRot.y instRot.z
							--@HACK MAX says rotations are right-handed, but seems to rotate in the left-handed way...
							--Perhaps things just aren't being done in the world coordinate system.
							local euler = eulerAngles -instRot.x -instRot.y -instRot.z
							rotate inst (eulerToQuat euler order:1) -- rotate using Euler angles
							format "instance % mesh:%" instName meshName
							format " pos:% rot:% scl:%\n" instPos instRot instScale
						) else (
							format "NOTE: Mesh % for instance % not found.\n" meshName instName
						)
					)
				)
			) -- endif parseMode
			
		) -- endif while !eof
			
		close i_stream
		-- remove the extra meshes that we now have instances of
		for m in usedMeshes do (
			delete m
		)
		
		max views redraw
	)
	
	--------------------------
	-- Main Import Function --
	--------------------------
	fn ResetMAX = (
		resetMAXFile #noprompt
		max utility mode
	)

	on importTPMButton pressed do
	(

		O_File = getOpenFileName "Import TPM" types:"TPM Files (*.tpm)|*.tpm|All Files (*.*)|*.*"
		if O_File != undefined then (
			if resetCheck.checked then
				ResetMAX()
			ImportTPM O_File
		)
	)
	on testImportTPMButton pressed do
	(
		ResetMAX()
		ImportTPM "D:/Andres/Temp/bonetest.tpm"
		--ImportTPM "D:/Andres/Temp/test.txt"
		--ImportTPM "D:/Andres/Temp/crate.tpm"
		max utility mode
		if false then (
			for o in objects do (
				local bob = ""
				format "name: % % %\n" o o.name (classof o)
				if (classof o) == Editable_Mesh then (
					format " name:% %\n" o.name (classof o.mesh)
					--format " mesh:%\n" o.mesh.name
					format " pos:%\n" o.pos
				)
			)
		)
	)


)
