-- Skeleton exporter for RACER
-- Written for studio max 2009
-- Exports bones of a selected item and adds animation
-- Mitch VL 1-7-09
-- Cruden BV

-- remarks:
-- max uses flipped quats internally, so we have to flip them ourselves



macroScript Skeleton_Exporter category:"Skeleton Exporter" buttonText:"Exp. Skeleton"  tooltip:"Skeleton Exporter" 
(


	-- globals 
	
	DBoneExtension = ".DSkeleton"
	defaulttransform =  (matrix3 (point3 1 0 0) (point3 0 0 1) (point3 0 -1 0) (point3 0 0 0))

	---------------------
	-- Convencience --
	--------------------

	function GetGeometry o = 
	-- Checks if node is valid
	(
		Superclassof o == Geometryclass and classof o != TargetObject and 
		(classof o == Editable_Poly or classof o == Editable_Mesh or classof o == PolyMeshObject)

	)

	function RemoveExtension tstr =
	-- Removes the extension of a string, simple
	(
		local ind = findString tstr "."
		if ind != undefined then (tstr = substring tstr 1 (ind-1))
		return tstr
	)

	function ReplaceSlashes tstr = 
	-- replace the slash thingies
	(
		local findindex = 0
		while findindex != undefined do
		(
			findindex = findstring tstr @"\"
			if findindex != undefined then tstr = replace tstr findindex 1 @"/"
		)
		return tstr
	)

	function DoesBoneExistInSkin skinmod bonename=
	(
		for bi = 1 to skinops.getNumberBones skinmod do
		(
			if( (skinops.GetBoneName skinmod bi 1) == bonename) then return true
		)			
		return false
	)

	--------------------------------------
	-- Animation, bones and skinning --
	--------------------------------------
	function getBoneFromSkin theSkin boneID=
	(
		local boneArray=for o in objects where (refs.dependencyLoopTest theSkin o) collect o
		local boneName=(skinOps.GetBoneName theSkin boneID 1)
		local notFound=true
		local i=0
		while notFound and i<boneArray.count do
		(
			i+=1
			if boneArray[i].name==boneName then notFound=false
		)
		if notFound then undefined else boneArray[i]
	)

	function PrintBonesAndWeights skinmod obj nfile = 
	(
		local startmat = matrix3 1
		tmesh = snapshotAsMesh  obj
		
		in coordsys world
		(
			format "*SKELETONINFO {\n " to:nfile
				format "\t*VERTCOUNT %\n "  (getnumverts tmesh) to:nfile
				format "\t*INDEXCOUNT %\n "  ((getnumfaces tmesh)*3) to:nfile
				format "\t*FRAMERATE %\n" frameRate to:nfile
			format "}\n" to:nfile
			
			
			for bi = 1 to skinops.getNumberBones skinmod do
			(
				bonenode = getBoneFromSkin skinmod bi
				-- Print
				format "*BONE {\n " to:nfile
					format "\t*BONENAME %\n "  (skinops.GetBoneName skinmod bi 1) to:nfile
					format "\t*VERTEXWEIGHTS {\n " to:nfile	
						format "\t\t*MESHNAME %\n "  obj.name to:nfile
						for i = 1 to (skinOps.GetNumberVertices skinmod) do
						(
							for j = 1 to (skinOps.GetVertexWeightCount skinMod i) do
							(	
								boneID = (skinOps.GetVertexWeightBoneID skinMod i j)
								VertWeight = (skinOps.GetVertexWeight skinMod i j)
								if boneID == bi and VertWeight>0 then
								(
									format ("\t\t*WEIGHT % %\n")  (i-1) VertWeight to:nfile
								)
							)
						)
					format "\t}\n" to:nfile					
					format "\t*PARENTNAME %\n "  ( if bonenode.parent != undefined then ( 
									 if	(DoesBoneExistInSkin skinmod bonenode.parent.name) == true   then ( 
									 bonenode.parent.name) else "NULL") else "NULL") to:nfile -- i know ... 
					at time 0
					(
						-- quat invertion
						startmat = translate ((inverse bonenode.transform.rotationpart) as matrix3) bonenode.transform.translationpart 
						
						startmat = defaulttransform * startmat
						
				
						format "\t*POSXYZ % % %\n " startmat.translationpart.x startmat.translationpart.z  -startmat.translationpart.y to:nfile
						format "\t*ROXYZW % % % %\n " startmat.rotationpart.x startmat.rotationpart.y startmat.rotationpart.z startmat.rotationpart.w to:nfile
						format "\t*SCAXYZ % % %\n " startmat.scalepart.x startmat.scalepart.x startmat.scalepart.z  to:nfile
					)
					
					--local blength = ((bonestart skinops.getStartpoint skinmod bi) - (skinops.getEndpoint skinmod bi)).length
				
					--if(bonenode.length!=undefined) then tlength = bonenode.length
					local tlength =  (length (bonenode.center- bonenode.transform.translationpart)) * 2.0
					format "\t*LENGTH %\n " tlength  to:nfile
					format "\t*ANIMATION {\n" to:nfile
						
					
						-- reset startmatrix
						at time 0
						(
							startmat = bonenode.transform
							if bonenode.parent != undefined then 
							(
								-- set the startmatrix relative to its parent
								local parentstartmat = matrix3 1
								parentstartmat =  bonenode.parent.transform
								startmat = startmat * (inverse parentstartmat)
							)
						)
						
						-- get the animation of the bones
						for ttime=animationrange.start to animationrange.end do
						(
							at time ttime 
							(
								local endmat = bonenode.transform
								
								if bonenode.parent != undefined then
								(
									local parentmat = bonenode.parent.transform
																
									endmat = endmat * (inverse parentmat)
								) 
								
								endmat = endmat * (inverse startmat)
								
								endmat =  (translate ((inverse endmat.rotationpart) as matrix3) endmat.translationpart )
								
							
								-- print
								format "\t\t*ANITIME %\n " ttime to:nfile
								format "\t\t*ANIPOSXYZ % % %\n " endmat.translationpart.y endmat.translationpart.x endmat.translationpart.z to:nfile
								format "\t\t*ANIROXYZW % % % %\n " endmat.rotationpart.x endmat.rotationpart.y endmat.rotationpart.z endmat.rotationpart.w to:nfile
								format "\t\t*ANISCAXYZ % % %\n " endmat.scalepart.x endmat.scalepart.y endmat.scalepart.z to:nfile
							)
						)
					

					format "\t}\n" to:nfile			
				format "}\n" to:nfile
			) -- for 
		) -- coordsys world
	) -- end of func



	function AddAnimationBones obj nfile = 
	-- find the skinning stuff
	(
		skinmod =  obj.modifiers[#Skin]

		if skinmod != undefined and (skinops.getNumberBones skinmod) > 0 then 
		(
			-- select the modifier
			max modify mode
			modPanel.SetCurrentObject skinmod node:obj true
			
			-- Let's Print !
			PrintBonesAndWeights skinmod obj nfile

		)	
	)

	----------------------------
	-- main() (not really ...) --
	----------------------------

	on execute do
	(
		if selection.count == 1 and GetGeometry selection[1] then
		(
			-- We use the selected item
			obj = selection[1]
			if isValidNode obj  and obj != undefined then
			(
				outname = GetSaveFileName types:DBoneExtension
				if outname != undefined then
				(
					-- extension checks
					outname = RemoveExtension outname
					outname += DBoneExtension
					
					-- remove slashes, replace with the other ones
					outname = ReplaceSlashes outname 
					
					--open the file for writing and reading
					nfile = openfile outname mode:"w+" 
				
					if nfile != undefined then
					(
						-- add racer extras
						AddAnimationBones obj nfile 
						
						-- cleanup
						close nfile
						--edit outname -- open outputfile
					) 
					else messagebox "Error in creating file for adding Extra's"
				)
				-- cancelled
			)
			else messagebox "Select a valid object for export"
		)
		else messagebox "Select a valid object for export"
	)	
)