--------------------------------------------------------------------------------
--	Script file guard
--	Examine its value to see how many times the script was run.
--------------------------------------------------------------------------------
if (guardBuildingMill == undefined) then
(
	global guardBuildingMill = 0
)	

guardBuildingMill += 1

--------------------------------------------------------------------------------
--	Make sure TB2 starup was executed first.
--------------------------------------------------------------------------------

if (guardTB2Startup == undefined) then
(
	global guardTB2Startup = 0
	FileIn ((getDir #startupScripts) + "\\TB2Startup.ms")
)

----------------------------------------------------------------------------------------------------
-- Includes
----------------------------------------------------------------------------------------------------

FnTB2FileInOnce "ScriptUtils.ms"	guardScriptUtils
FnTB2FileInOnce "ModelNames.ms" 	guardModelNames
FnTB2FileInOnce "CameraRig.ms" 	guardCameraRig

--------------------------------------------------------------------------------------
-- Constants
----------------------------------------------------------------------------------------------------

-- constants to test if a mat is a multimat
kMultiMatClass1 	= 512
kMultiMatClass2 	= 0

-- local constansts - sort of
kNightMaterialID	= 15
-- we're disabling this feature just before Beta
kNightBlurRadius 	= #(0.001, 0.001, 0.001, 0.001, 0.001) 

-- parameters by zoom level for night texture generation
kMaskScaleFactor 	= #(2, 2, 1, 1, 1) -- amount of normal window mask to mix into result alpha
kBlurScaleFactor 	= #(8, 5, 3, 2, 1) -- amount of blured window mask to mix into result alpha

kNigthColor			= (Point3 .501961 .470588 .752941)

kWindowSetStr		= "TB2_WindowSet"

windowTextureMapArray 	= #() -- initialize this at first export

kBuildingTypeStr 		= "TB2_BuildingType"
kLegalBuildingTypes 	= #("building", "foundation")

kWindowOpacityFileProperty = "Window Opacity"
gWindowOpacityMin		= 0.0
gWindowOpacityMax		= 1.0


----------------------------------------------------------------------------------------------------
-- Function:	FnZoomRotInfoString 
-- Param:		zom
-- Param:		rot
----------------------------------------------------------------------------------------------------

fn FnZoomRotInfoString zom rot = 
(
	local str = "Zoom: " + (zom as string) + " View: " 
	
	case rot of
	(
		1:	str += "South"
		2:	str += "East"
		3:	str += "North"
		4:	str += "West"
	)
	
	return str
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetBoundsNearestCorner 
-- Param:		nodes	- collection of nodes
-- Param:		dir		- vector pointing in far direction (the camera view vector) 
----------------------------------------------------------------------------------------------------
fn FnGetBoundsNearestCorner nodes dir =
(
	local maxPt, minPt, closePt
	
	maxPt = nodes.max
	minPt = nodes.min
	
	closePt = (point3 0 0 0)
	
	if (dir.x > 0) then (
		closePt.x = minPt.x
	)
	else (
		closePt.x = maxPt.x
	)
	
	if (dir.y > 0) then (
		closePt.y = minPt.y
	)
	else (
		closePt.y = maxPt.y
	)
	
	if (dir.z > 0) then (
		closePt.z = minPt.z
	)
	else (
		closePt.z = maxPt.z
	)
	
	return closePt
)

----------------------------------------------------------------------------------------------------
-- Function:	FnRemoveBackFaces 
-- Param:		objMesh - object to be culled
-- Param:		dir     - camera direction
----------------------------------------------------------------------------------------------------
fn FnRemoveBackFaces objMesh dir = 
(
	local numFaces,index,norm,faceList
	
	faceList = #()
	
	numFaces = getNumFaces objMesh
	
	-- get list of back facing faces
	for index in 1 to numFaces do 
	(
		norm = getFaceNormal objMesh index
		if (dot norm dir) > 0 then 
		(
			append faceList index
		)
	)
	
	delete objMesh.Faces[faceList]
)

----------------------------------------------------------------------------------------------------
-- Function:	FnMeshLocalToWorld 
-- Param:		obj
----------------------------------------------------------------------------------------------------
fn FnMeshLocalToWorld obj = 
(
	-- Get the original node transform matrix
	local ntm = obj.transform
	
	-- The new object transform should be identity
	obj.transform= (matrix3 1)
	
	-- Compute the pivot transform matrix
	local piv=obj.objecttransform 
	
	-- Reset the object offsets to 0
	obj.objectoffsetPos  = [0,0,0]
	obj.objectoffsetRot = (quat 0 0 0 1)
	obj.objectoffsetScale = [1,1,1]
	
	-- Apply the pivot transform matrix to the modified original node transform matrix
	-- to get the XForm gizmo transform
	ntm = piv * ntm
	
	-- apply an XForm modifier to the node
	local xformMod=xform()
	addmodifier obj xformMod
	
	-- set the XForm modifier's gizmo tranform
	xformMod.gizmo.transform=ntm
	
	-- finally, collapse everything back to an editable mesh
	collapseStack obj
	
	-- return value of ok
	ok
)

----------------------------------------------------------------------------------------------------
-- Function:	FnApplyScreenRegionUVMapping 
-- Param:		obj 
-- Param:		scrnBox 
-- N.B.			It is assumed that object is an editMesh at this point
----------------------------------------------------------------------------------------------------
fn FnApplyScreenRegionUVMapping obj renderBox = 
(
	local vert, camPt, uvwPt, vrtIndx

	meshop.defaultMapFaces obj.mesh 1

	-- set transform to object local
	gCameraRig.localToWorldXform = obj.objecttransform
	
	for vrtIndx in 1 to obj.mesh.numverts do 
	(
		vert = getVert obj.mesh vrtIndx
		
		--print vert
		
		-- transform object local coordinates to screen
		camPt = gCameraRig.GetRenderPos vert
		
		-- round to nearest pixel position
		camPt.x = floor (camPt.x + .5)
		camPt.y = floor (camPt.y + .5)

		--print camPt
		
		--format "UV: %,%\n" ((camPt.x-renderBox.x)/(renderBox.w-1)) (1-((camPt.y-renderBox.y)/(renderBox.h-1)))
		setTVert obj.mesh vrtIndx ((camPt.x-renderBox.x)/(renderBox.w-1)) (1-((camPt.y-renderBox.y)/(renderBox.h-1))) 0
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnApplyDicedRegionUVMapping 
-- Param:		obj 
-- Param:		renderBox 
----------------------------------------------------------------------------------------------------
fn FnApplyDicedRegionUVMapping obj renderBox =
(
	local vert, cornerPt, uvwPt, vrtIndx, u, v
	local avgPt = (point3 0 0 0)

	--local debugStream
	--debugStream = listener -- newScript()
	
	--format "Mesh : %\n" obj.name to:debugStream
	--format "renderBox: %\n" renderBox to:debugStream
	
	meshop.defaultMapFaces obj.mesh 1
	
	-- set transform to object local
	gCameraRig.localToWorldXform = obj.objecttransform
	
	-- guess at corner location from average mesh vert
	for vrtIndx in 1 to obj.mesh.numverts do 
	(
		vert = getVert obj.mesh vrtIndx
		avgPt.x += vert.x
		avgPt.y += vert.y
		avgPt.z += vert.z
	)
	
	avgPt.x = avgPt.x/obj.mesh.numverts
	avgPt.y = avgPt.y/obj.mesh.numverts
	avgPt.z = avgPt.z/obj.mesh.numverts
	
	camPt = gCameraRig.GetRenderPos avgPt
	
	-- round to nearest pixel position
	camPt.x = floor (camPt.x + .5)
	camPt.y = floor (camPt.y + .5)
	
	-- which 256*256 page did that point fall in?
	cornerPt = (point2 0 0)
	cornerPt.x = renderBox.x + ((((camPt.x-renderBox.x) as integer)/256)*256)
	cornerPt.y = renderBox.y + ((((camPt.y-renderBox.y) as integer)/256)*256)
	
	--format "camPt : %\n" camPt to:debugStream
	--format "cornerPt : %\n" cornerPt to:debugStream
	
	for vrtIndx in 1 to obj.mesh.numverts do 
	(
		vert = getVert obj.mesh vrtIndx
		
		--format "vert : %\n" vert to:debugStream

		-- transform object local coordinates to screen
		camPt = gCameraRig.GetRenderPos vert
		
		--format "camPt : %\n" camPt to:debugStream
		
		-- round to nearest pixel position
		camPt.x = floor (camPt.x + .5)
		camPt.y = floor (camPt.y + .5)
		
		--format "camPt : %\n" camPt to:debugStream
		
		u = (camPt.x-cornerPt.x)/256.0
		v = 1-((camPt.y-cornerPt.y)/256.0)
		
		--format "u v : % %\n" u v to:debugStream

		setTVert obj.mesh vrtIndx u v 0
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnMakeSlabs 
-- Param:		meshNode	- source mesh to slice, not changed by procedure, must have 1 to 1 mapping verts (results from meshop.defaultMapFaces objmesh 1)
-- Param:		numSlabs	- number of slabs to generate
-- Param:		normal		- normal to slice planes
-- Param:		base		- the origin of the slices (each slice offset = base + index*step )
-- Param:		step		- thickness of slabs relative to normal
-- Param:		parts		- array to accumulate the generated slab
----------------------------------------------------------------------------------------------------
fn FnMakeSlabs meshNode numSlabs normal base step parts =
(
	local aboveMesh, belowMesh
	local slabIndex, vertIndex
	local vert, tVert, mapVal
	local offset
	
	offset = base
	aboveMesh = copy meshNode
	
	for slabIndex in 1 to numSlabs do 
	(
		offset += step
		
		belowMesh = copy aboveMesh
		belowMesh.name = uniqueName "TB2_DiceTemp"
		
		meshop.slice aboveMesh aboveMesh.Faces normal offset delete:true node:meshNode 
		meshop.slice belowMesh belowMesh.Faces (-normal) (-offset) delete:true node:meshNode
		
		meshop.deleteIsoVerts aboveMesh.mesh
		meshop.deleteIsoVerts belowMesh.mesh
		
		meshop.optimize aboveMesh.mesh 40 0 0.1 0 saveSmoothBoundaries:false autoEdge:false
		meshop.optimize belowMesh.mesh 40 0 0.1 0 saveSmoothBoundaries:false autoEdge:false
		
		meshop.deleteIsoVerts aboveMesh.mesh
		meshop.deleteIsoVerts belowMesh.mesh
		
		update aboveMesh 
		update belowMesh
		
		append parts belowMesh
		
		lastOffset = offset
	)
	
	-- at this point aboveMesh is empty
	delete aboveMesh 
	
	return parts
)

----------------------------------------------------------------------------------------------------
-- Function:	SetWindowSet 
-- Param:		setName 
-- Desc:		Sets window set number in file properties.
----------------------------------------------------------------------------------------------------
fn SetWindowSet setName = 
(
	if (setName  !=undefined )then 
	(
		fileProperties.addProperty #custom kWindowSetStr setName
		
		-- apparently Max doesn't notice when you change custom properties
		-- So... I'm messing with the scene a little to force Max to treat the scene as dirty
		local dummyNode = (box())
		delete dummyNode 
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	GetWindowSet 
-- Desc:		Gets window set number from file properties.
----------------------------------------------------------------------------------------------------
fn GetWindowSet = 
(
	local typeIndex = (fileProperties.findProperty #custom kWindowSetStr)
	local setName	 = ""
	
	if (typeIndex > 0) then 
	(
		setName = fileProperties.getPropertyValue #custom typeIndex
	)
	else
	(
		local localSets = GetNightWindowSets()
		
		if (localSets != undefined and localSets.count > 1) then
		(
			local setArray = filterstring localSets ";"
			
			if (setArray.count > 0) then
			(
				setName = setArray[1]
				SetWindowSet setName
			)
		)
	)
	
	return setName
)

----------------------------------------------------------------------------------------------------
-- Function:	GetModelType 
-- Desc:		Gets model type (#Building or #Foundation) from file properties.  
----------------------------------------------------------------------------------------------------
fn GetModelType = 
(
	local typeIndex = (fileProperties.findProperty #custom kBuildingTypeStr)
	
	if (typeIndex > 0) then 
	(
		local typeStr = fileProperties.getPropertyValue #custom typeIndex
		if ((findItem kLegalBuildingTypes typeStr)>0) then 
		(
			return (typeStr as name)
		)
		else 
		(
			FnErrorMessage ("Unknown building type property '" + typeStr + "' set in custom file properties. Please set the value to a legal type with the building mill rollout.")
		)
	)
	
	return #Building
)

----------------------------------------------------------------------------------------------------
-- Function:	SetModelType 
-- Param:		modelType 
-- Desc:		Sets model type in file properties.
----------------------------------------------------------------------------------------------------
fn SetModelType modelType = (
	local typeStr = (modelType as string)
	if ((findItem kLegalBuildingTypes typeStr)>0) then (
		fileProperties.addProperty #custom kBuildingTypeStr typeStr 
		
		-- apparently Max doesn't notice when you change custom properties
		-- So... I'm messing with the scene a little to force Max to treat the scene as dirty
		local dummyNode = (box())
		delete dummyNode 
	)
	else (
		FnErrorMessage ("Attempting to set illegal building type property:'" + typeStr +"'\nThis is a script bug.")
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	GetWindowOpacity 
-- Desc:		Gets night windows opacity from file properties.  
----------------------------------------------------------------------------------------------------
fn GetWindowOpacity absolute = 
(
	local idx = (fileProperties.findProperty #custom kWindowOpacityFileProperty)
	if (idx > 0) then 
	(
		local opacity = fileProperties.getPropertyValue #custom idx
		if ((opacity != undefined) and (opacity >= gWindowOpacityMin) and (opacity <= gWindowOpacityMax)) then 
		(
			if absolute then
			(
				return opacity
			)
			else
			(
				return gWindowOpacityRelMin + opacity * (gWindowOpacityRelMax - gWindowOpacityRelMin)
			)

		)
		else 
		(
			FnErrorMessage ("Invalid Window Opacity '" + (opacity as string) + "' set in custom file properties. Please set the value using the Building Mill rollout.")
		)
	)
	
	return 1.0
)

----------------------------------------------------------------------------------------------------
-- Function:	SetWindowOpacity 
-- Param:		opacity 
-- Desc:		Sets night windows opacity in file properties.  
----------------------------------------------------------------------------------------------------
fn SetWindowOpacity opacity = 
(
	if ((opacity != undefined) and (opacity >= gWindowOpacityMin) and (opacity <= gWindowOpacityMax)) then 
	(
		fileProperties.addProperty #custom kWindowOpacityFileProperty opacity
		
		-- apparently Max doesn't notice when you change custom properties
		-- So... I'm messing with the scene a little to force Max to treat the scene as dirty
		--local dummyNode = (box())
		--delete dummyNode 
	)
	else 
	(
		FnErrorMessage ("Attempting to set illegal Window Opacity:'" + (opacity as string) +"'\nThis is a script bug.")
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnEnableNightLigts 
-- Param:		doEnable (true - enable; false - disable) 
----------------------------------------------------------------------------------------------------
fn FnEnableNightLigts doEnable =
(
	if ($lights.count > 0) do 	-- scene has local night lights
	(
		for lt in $lights do 
		(
			-- $lights collection includes targets & targets don't have 'enabled' member
			if (lt.classID[1] != 4128) do 
			(
				local namePrefix = (substring lt.name 1 8)
				
				if (namePrefix == "nitelite") then
				(
					lt.enabled = doEnable 
				)
				else
				(
					lt.enabled = not doEnable 
				)
			)
		)
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnDoRender2 
-- Param:		renderBox 	
-- Param:		renderNight 
----------------------------------------------------------------------------------------------------
fn FnDoRender2 renderBox renderNight display:#all = 
(
	-- render the high res building to a bitmap
	-- set the active viewport to our camera so we can do a region render
	viewport.setCamera gCameraRig.curCam -- I believe this is no longer necessary - pit's just what we're used to seeing
	
	local zoomlevel 	= gCameraRig.curZoom
	local rotation 	= gCameraRig.curRot
	local captionInfo	= FnZoomRotInfoString zoomlevel rotation
	local displayDay	= (display == #all or display == #day)
	local tempCamera 	= gCameraRig.CreateRenderCam renderBox
	
	SetNightWindowOpacity 	(GetWindowOpacity false)
	FnEnableNightLigts false	-- disable local night lights for first render	
	
	rendHidden 		= false 		-- this stung us once force it off
	
	renderEffects 	= false		-- disable render effects
	RenderScene displayDay ("Day render. " + captionInfo)
	renderEffects 	= true		-- enable back render effects
	
	if (renderNight) do 
	(
		--local windowSetOffset 	= (GetWindowSet() - 1)*5
		local displayLights		= (display == #all or display == #lights)
		
		SetNightWindowMatID 		kNightMaterialID
		--SetNightWindowOpacity 	(GetWindowOpacity true)
		SetNightMaskScale			kMaskScaleFactor[zoomlevel]
		SetNightBlurRadius		kNightBlurRadius[zoomlevel] 
		SetNightBlurScale			kBlurScaleFactor[zoomlevel]
		--SetNightWindowMap 		windowTextureMapArray[zoomlevel+windowSetOffset]
		SetNightWindowMap 		(GetWindowSet()) zoomlevel
		
		-- deeper magic
		if ($nitelite*.count > 0) do 
		(
			-- enable local night lights for second render	
			FnEnableNightLigts true	
			
			-- turn off ambient too
			local saveColor = ambientColor
			ambientColor = (color 0 0 0)
			
			-- crank down lighting level
			local saveLight = lightLevel
			lightLevel = 0.1
				
			-- now we have to disable the xref in order to turn off the daytime lights (scarry)
			local xref1 = xrefs.getXRefFile 1
			xref1.disabled = true
			
			-- do night render
			RenderNightLights displayLights ("Night lights. " + captionInfo)
		
			-- restore xref
			xref1.disabled = false
			
			-- restore the current camera
			gCameraRig.RestoreCam()
			
			-- restore ambient & lighting level
			ambientColor 	= saveColor
			lightLevel 		= saveLight
		)
		
		MakeNightMap()
	)
	
	if tempCamera != undefined then
	(
		delete tempCamera
	)
)


---------------------------------------------------------------------------------------------------
-- Function:	FnRenderThumbnails 																						
-- Desc:																															
---------------------------------------------------------------------------------------------------
fn FnRenderThumbnails = 
(
	if (not gCameraRig.SetThumbnailCam()) then
	(
		local errorMsg = "Could not find or create thumbnail camera!"
		messageBox errorMsg
		return false
	)
	
	hide $LOD*
	hide $TB2_TempLOD*
	
	--SetAALevel gAALevel	
	SetNightWindowOpacity 	(GetWindowOpacity false)		
	SetNightWindowMatID 		kNightMaterialID
	SetNightMaskScale			kMaskScaleFactor[5]
	SetNightBlurRadius		kNightBlurRadius[5] 
	SetNightBlurScale			kBlurScaleFactor[5]
	SetNightWindowMap 		(GetWindowSet()) 5
	
	FnEnableNightLigts false	-- disable local night lights for first render	
	
	rendHidden 			= false 		-- this stung us once force it off
	
	local curBgColor 	= backgroundColor
	backgroundColor 	= gThumbnailBgColor
	AntialiasBackground		true
	--SetAALevel					gAALevel
	--SetAADiameter				gAADiameter
	--SetAABlur					gAABlur

	RenderScene true "Thumbnail"
	
	if ($nitelite*.count > 0) do 
	(
		-- enable local night lights for second render	
		FnEnableNightLigts true	

		-- turn off ambient too
		local saveColor = ambientColor
		ambientColor = (color 0 0 0)

		-- crank down lighting level
		local saveLight = lightLevel
		lightLevel = 0.1

		-- now we have to disable the xref in order to turn off the daytime lights (scarry)
		local xref1 = xrefs.getXRefFile 1
		xref1.disabled = true

		-- do night render
		
		backgroundColor = kNigthColor
		RenderNightLights false "Thumbnail: Night lights."
		
		-- restore xref
		xref1.disabled = false

		-- restore ambient & lighting level
		ambientColor 	= saveColor
		lightLevel 		= saveLight
	)

	MakeNightMap()
	
	ShowNightPreview kNigthColor ("Night thumbnail.")

	AntialiasBackground		false
	--SetAALevel					gAALevel
	backgroundColor = curBgColor 
		
	return true
)

----------------------------------------------------------------------------------------------------
-- Function:	FnCreateModelMaterial 
-- Param:		meshName 
-- Param:		textureFile 
----------------------------------------------------------------------------------------------------
fn FnCreateModelMaterial meshName textureFile = 
(
	local theMaterial = (StandardMaterial diffuse:[128,128,128] )
	
	theMaterial.name = meshName
	theMaterial.diffusemap = (BitmapTexture filename:textureFile filtering:2)
	theMaterial.opacitymap = (BitmapTexture filename:textureFile monoOutput:1)
	showTextureMap theMaterial theMaterial.diffusemap on
	theMaterial.selfIllumAmount = 100.0
	
	return theMaterial
)

----------------------------------------------------------------------------------------------------
-- Function:	FnRenderAndMapSimpleShell
-- Param:		geomNode 
-- Param:		renderBox 
-- Param:		modelFile 
-- Opt Param:	doNight:false
--	N.B.			This is the new implementation
----------------------------------------------------------------------------------------------------
fn FnRenderAndMapSimpleShell geomNode renderBox modelFile doNight:false =
(
	-- Apply mapping to the shell
	FnApplyScreenRegionUVMapping geomNode renderBox 
	update geomNode.mesh
	
	-- make sure none of the shell nodes show up in our render
	hide geomNode
	hide $LOD*
	hide $TB2*
	
	FnDoRender2 renderBox doNight
	
	local dayTexName = modelFile.TextureOutputName 0

	SaveMapToFile #dayMap dayTexName
	SaveMapToDAT  #dayMap (modelFile.TextureOutputGUID())
	
	if (doNight) then 
	(
		local nightTexName = modelFile.NightTextureName 0
		
		SaveMapToFile #nightMap nightTexName
		SaveMapToDAT  #nightMap (modelFile.NightTextureGUID())
	)
		
	-- assign a material to the node
	geomNode.material = FnCreateModelMaterial (modelFile.MeshName 0) dayTexName
	
	ok
)

----------------------------------------------------------------------------------------------------
-- Function:	FnRenderAndMapDicedShell
-- Param:		geomNode 
-- Param:		renderBox 
-- Param:		modelFile 
-- Opt Param:	doNight:false
--	N.B.			This is the new implementation
----------------------------------------------------------------------------------------------------
fn FnRenderAndMapDicedShell geomNode renderBox modelFile doNight:false =
(
	local sliceVector, pixelPt
	local baseOffset, slabStep, numRows, numCols
	local coordSysTM 	= gCameraRig.curCam.transform
	local rowNodes 	= #()
	local resultNodes = #()
	local rowIndex
	local colIndex
	local nodeIndex
	local meshIndex
	local curNode
	
	-- geomNodes' mesh must be in world space for the following to work
	if (renderBox.h > 257) then 
	(
		-- we need to dice vertically
		
		numRows 		= ceil ((renderBox.h-1)/256.0)
		sliceVector = -coordSysTM.row2
		normalize sliceVector 
		
		-- setup parameters for MakeSlabs
		pixelPt 		= gCameraRig.GetPixelWorldPoint (point2 renderBox.x renderBox.y) 
		baseOffset 	= dot sliceVector pixelPt
		
		pixelPt 		= gCameraRig.GetPixelWorldPoint (point2 renderBox.x (renderBox.y+256))
		slabStep		= (dot sliceVector pixelPt) - baseOffset 
		
		FnMakeSlabs geomNode numRows sliceVector baseOffset slabStep rowNodes 
	)
	else 
	(
		-- don't need vertical slice, seed rowNodes with node for column slicing
		append rowNodes (copy geomNode)
		numRows = 1
	)
	
	if (renderBox.w > 257) then 
	(
		
		numCols 		= ceil ((renderBox.w-1)/256.0)
		sliceVector = coordSysTM.row1
		normalize sliceVector 
		
		-- setup parameters for MakeSlabs
		pixelPt 		= gCameraRig.GetPixelWorldPoint (point2 renderBox.x renderBox.y) -- we just need the point part of the ray actually, but this is a handy routine
		baseOffset 	= dot sliceVector pixelPt
		
		pixelPt 		= gCameraRig.GetPixelWorldPoint (point2 (renderBox.x+256) renderBox.y)
		slabStep 	= (dot sliceVector pixelPt) - baseOffset 
		
		--format "baseOffset : % slabStep %\n" baseOffset slabStep to:debugStream
		
		for rowIndex in 1 to rowNodes.count do 
		(
			FnMakeSlabs rowNodes[rowIndex]  numCols sliceVector baseOffset slabStep resultNodes 
		)
		
		delete rowNodes 
	)
	else 
	(
		-- don't need horizontal slice copy result from rows
		resultNodes = rowNodes
		numCols = 1
	)
	
	-- make sure none of the shell nodes show up in our render
	hide resultNodes
	hide geomNode
	hide $LOD*
	hide $TB2*
	
	-- generate source bitmap for dicing
	
	FnDoRender2 renderBox doNight
	
	nodeIndex = 1 -- current index in result array
	meshIndex = 0 -- index of valid output mesh (skips empty meshes in resultNodes)
	
	for rowIndex in 1 to numRows do 
	(
		for colIndex in 1 to numCols do 
		(
			curNode = resultNodes[nodeIndex]
			
			if ((curNode.mesh.numVerts > 0) and (curNode.mesh.numFaces > 0)) do 
			(
				FnApplyDicedRegionUVMapping curNode renderBox 
				local texFilename = modelFile.TextureOutputName meshIndex
				print texFilename
				print (modelFile.TextureOutputGUID texIndex:meshIndex as string)
				
				local cropBox = Box2 ((colIndex-1)*256) ((rowIndex-1)*256) 256 256
				
				SaveMapToFile #dayMap texFilename cropRect:cropBox
				SaveMapToDAT  #dayMap (modelFile.TextureOutputGUID texIndex:meshIndex) cropRect:cropBox

				--create material and assign texmaps
				curNode.material = FnCreateModelMaterial (modelFile.MeshName meshIndex) texFilename
				
				if (doNight) do 
				(
					-- do it again for the night mask
					texFilename = modelFile.NightTextureName meshIndex
					
					SaveMapToFile #nightMap texFilename cropRect:cropBox					
					SaveMapToDAT  #nightMap (modelFile.NightTextureGUID texIndex:meshIndex) cropRect:cropBox	
				)
				
				meshIndex += 1
			)
			
			nodeIndex += 1
		)
	)
	
	-- almost there, now go through result nodes and clear out any empty meshes
	for nodeIndex in resultNodes.count to 1 by -1 do 
	(
		curNode = resultNodes[nodeIndex]
		if ((curNode.mesh.numVerts == 0) or (curNode.mesh.numFaces == 0)) do 
		(
			--fnErrorMessage ("Deleting: " + curNode.name)
			deleteItem resultNodes nodeIndex
			
			delete curNode
		)
	)
	
	return resultNodes 
)

---------------------------------------------------------------------------------------------------
-- Function:	FnCreateLowPolyShell 																					
-- Param:		lodShell 	- Artist created low poly shell														
-- Param:		modelFile 	- stModelName struct to generate filenames										
-- Opt Param:	doNight:false																								
-- Desc:			Creates and returns shell geometry node appropriate for export to S3D format			
---------------------------------------------------------------------------------------------------
fn FnCreateLowPolyShell lodShell modelFile doNight:false =
(
	-- make a copy of the supplied LOD shell because we are going to mangle it
	-- to make a view dependant version of it
	local geomNode = copy lodShell 
	
	-- make sure building meshes are visible
	-- max unhide all 

	-- give this mesh a unique name so it doesn't get confused with others
	geomNode.name = uniqueName "TB2_ShellTemp"
	
	-- make sure it's an editmesh so modifications below can work
	convertToMesh geomNode
	
	-- make sure that the geometry is in world space so mods below can work
	FnMeshLocalToWorld geomNode
	
	-- remove backfaces 
	FnRemoveBackFaces geomNode (gCameraRig.GetViewDir()) 
	
	-- get the screen bounds of the shell that is nice texture size
	local renderBox = gCameraRig.GetCamRenderRegion geomNode
	
	-- is this a simple case, or do we need to dice
	if ((renderBox.h <=256) and (renderBox.w <= 256)) then 
	(
		FnRenderAndMapSimpleShell geomNode renderBox modelFile doNight:doNight
		
		-- unhide geomNode
		
		return geomNode
	)
	-- It's big, dice & return group
	else
	(
		
		local dicedNodes = FnRenderAndMapDicedShell geomNode renderBox modelFile doNight:doNight
		
		if (dicedNodes.count > 1) then 
		(
			local modelGroup
			
			delete geomNode
			-- unhide dicedNodes
			
			modelGroup = group dicedNodes prefix:"TB2_ShellGroup"
			
			return modelGroup
		)
		else 
		(
			if (dicedNodes.count == 1) then 
			(
				return dicedNodes[1]
			)
			else 
			(
				FnErrorMessage "Error: nothing left after Dice and Render. This is a script bug."
				return undefined
			)
		)
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetFrameFromZoomRot 
-- Param:		zoom
-- Param:		rot
----------------------------------------------------------------------------------------------------
fn FnGetFrameFromZoomRot zoom rot = 
(
	if ((zoom>0) and (zoom <= 5) and (rot > 0) and (rot <= 4)) then
	(
		return (5-zoom)*4 + (rot-1)
	)
	else
	(
		return -1 -- error case
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetZoomFromFrame 
-- Param:		frame
----------------------------------------------------------------------------------------------------
fn FnGetZoomFromFrame frame = 
(
	if ((frame >= 0) and (frame <= 19)) then
	(
		return 5 - ((frame as integer) / 4)
	)
	else
	(
		return -1
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetRotFromFrame 
-- Param:		frame
----------------------------------------------------------------------------------------------------
fn FnGetRotFromFrame frame = 
(
	if ((frame >= 0) and (frame <= 19)) then
	(
		return 1 + ((mod frame 4) as integer)
	)
	else
	(
		return -1
	)
)

----------------------------------------------------------------------------------------------------
-- Function:	FnMatHasEffectChan 
-- Param:		mtrl 
-- Param:		matID 
--	gmax:			gmax does not support material effect chanels
----------------------------------------------------------------------------------------------------
fn FnMatHasEffectChan mtrl matID = 
(
/*
	-- multimaterials
	if ((mtrl.classID[1] == kMultiMatClass1) and (mtrl.classID[2] == kMultiMatClass2)) then 
	(
		local submtrl
		
		for submtrl in mtrl.materialList do 
		(
			-- recurse because these could be multimats too
			if (FnMatHasEffectChan submtrl matID) do
			(
				return true
			)
		)
	)
	-- regular material
	else 
	(
		if (mtrl.effectsChannel == matID) do 
		(
			return true
		)
	)
*/
	false
)

----------------------------------------------------------------------------------------------------
-- Function:	FnSceneHasNightMats 
--	gmax:			gmax does not support material effect chanels
----------------------------------------------------------------------------------------------------
fn FnSceneHasNightMats = 
(
	return $*night*.count + $*nite*.count > 0
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetNodesToSave 
----------------------------------------------------------------------------------------------------
fn FnGetNodesToSave = 
(
	local nodesToSave = #()
	
	for node in rootNode.children do 
	(
		local object = node.baseObject
		if (superclassOf object == helper and  classOf object == point) then 
		(
			append nodesToSave node
		) 
	)
	
	return nodesToSave 
)
----------------------------------------------------------------------------------------------------
-- Function:	FnFindLOD 
----------------------------------------------------------------------------------------------------
fn FnFindLOD level = 
(
	local lodFound
	case level of
	(
		1:	lodFound= $LOD1
		2:	lodFound= $LOD2
		3:	lodFound= $LOD3
		4:	lodFound= $LOD4
		5:	lodFound= $LOD5
	)	
	return lodFound
)

----------------------------------------------------------------------------------------------------
-- Function:	FnCreateLOD 
----------------------------------------------------------------------------------------------------
fn FnCreateLOD level ask:true = 
(
	local curLOD
	local lodName = "LOD" + (level as string)
	
	case level of
	(
		3:	curLOD = $LOD3
		4:	curLOD = $LOD4
		5:	curLOD = $LOD5
		default: return undefined
	)
	
	if (curLOD == undefined) then
	(
		curLOD = FnCreateSceneBoundingBox name:lodName
		return curLOD 
	)
	
	local replaceLOD = true
	
	if ask then
	(
		replaceLOD = queryBox  (lodName + " already exists.\nReplace?")
	)
	
	if replaceLOD then
	(
		delete curLOD
		curLOD = FnCreateSceneBoundingBox name:lodName 
	)
	
	return curLOD
)

----------------------------------------------------------------------------------------------------
-- Function:	FnCreateLOD_3_4_5 
----------------------------------------------------------------------------------------------------
fn FnCreateLOD_3_4_5 = 
(
	if geometry.count == 0 then
	(
		return false
	)
	
	if ($LOD3 == undefined) then
	(
		FnCreateSceneBoundingBox name:"LOD3"
	)
	if ($LOD4 == undefined) then
	(
		local lod4 = copy $LOD3
		lod4.name = "LOD4"
	)
	if ($LOD5 == undefined) then
	(
		local lod5 = copy $LOD3
		lod5.name = "LOD5"
	)
	
	return true
)

----------------------------------------------------------------------------------------------------
-- Function:	FnGetLODNodes 
----------------------------------------------------------------------------------------------------
fn FnGetLODNodes &lods = 
(
	FnCreateLOD_3_4_5()
	
	local tempLOD1
	local tempLOD2
	
	-- creat a box that is the bounds of the LOD3 shell to use as the shell for lod 1 & 2
	
	tempLOD1 = box width:($LOD3.max.x - $LOD3.min.x) length:($LOD3.max.y - $LOD3.min.y) height:($LOD3.max.z - $LOD3.min.z) 
	tempLOD1.position.x += $LOD3.min.x - tempLOD1.min.x
	tempLOD1.position.y += $LOD3.min.y - tempLOD1.min.y
	tempLOD1.position.z += $LOD3.min.z - tempLOD1.min.z
	tempLOD1.name = "TB2_TempLOD1_Box"
	
	tempLOD2 = box width:($LOD3.max.x - $LOD3.min.x) length:($LOD3.max.y - $LOD3.min.y) height:($LOD3.max.z - $LOD3.min.z) 
	tempLOD2.position.x += $LOD3.min.x - tempLOD2.min.x
	tempLOD2.position.y += $LOD3.min.y - tempLOD2.min.y
	tempLOD2.position.z += $LOD3.min.z - tempLOD2.min.z
	tempLOD2.name = "TB2_TempLOD2_Box"
	
	lods = #($TB2_TempLOD1_Box, $TB2_TempLOD2_Box, $LOD3, $LOD4, $LOD5)
	
	-- need to make sure that all the shells are there
	
	return true 
)

----------------------------------------------------------------------------------------------------
-- Function:	FnCleanupDestFolder 
-- Param:		destpath	- destination folder
-- Desc:			Deletes all temporary files left from the previous export
----------------------------------------------------------------------------------------------------
fn FnCleanupDestFolder destpath = 
(
	FnDeleteFiles (destpath + "*.fsh")
	FnDeleteFiles (destpath + "*.s3d")
	FnDeleteFiles (destpath + "*.s3d.txt")
)

----------------------------------------------------------------------------------------------------
-- Function:	FnExportAllZoomsAndRotations
-- Param:		destpath	- destination folder
-- Desc:		Exports all 20 low poly models.
----------------------------------------------------------------------------------------------------
fn FnExportAllZoomsAndRotations destpath = 
(
	if geometry.count == 0 then
	(
		messageBox "Nothing to export. The scene is empty"
		return false
	)

	-- make sure we've got the right version of rendlx.dlx
	if (not FnPluginIsRightVersion()) do 
	(
		return false
	)
	
	if (not (PreExportCheck true)) then
	(
		return false
	)
	
	local startTime 		= timeStamp()
	local modelName 		= stModelName()
	local thisModelType 	= GetModelType() 	-- building or foundation
	local bHasNightMats 	= FnSceneHasNightMats() or ($nitelite*.count>0) -- don't process the night mask if there isn't one
	local nodesToSave		= FnGetNodesToSave()
	local	success 			= 0					-- used for debugging
	
	local numOfSteps		= 22				--	one for each view + one for thumbnails + one for initialization
	local curStep			= 0
	local datName

	AntialiasBackground	true
	SetAALevel				gAALevel
	SetAADiameter			gAADiameter
	SetAABlur				gAABlur
    
	try
	(
		setWaitCursor()
		progressStart "Exporting..."
		curStep += 1
		progressUpdate (100*curStep/numOfSteps)
		
		--	Check Camera
		
		if not gCameraRig.CheckCameras() then
		(
			local errorMsg = "Failed to set cameras!"
			messageBox errorMsg
			throw errorMsg
		)
		
		if not gCameraRig.ResetRig() then
		(
			local errorMsg = "Failed to reset cameras!"
			messageBox errorMsg
			throw errorMsg
		)
		
		-- Init a modelname structure
		if (not modelName.Init maxfilename destpath modelType:thisModelType) do 
		(
			local errorMsg = "Model name structure failed to initialize!"
			messageBox errorMsg
			throw errorMsg
		)
		
		datName = (destpath + (getFilenameFile maxFileName) + "-" + modelName.ModelGUID() + gExportExt)
		
		ExportStartup datName
		success += 1

		-- Delete oldfiles in temp folder
		FnDeleteFiles ((modelName.Destination which:#temp) + "*.*")
		-- Clean up destination folder
		FnCleanupDestFolder destpath
		-- Delete any dangling temp meshes that might have been created before
		delete $TB2_*
		success += 1

		-- Get or create the lod shells
		local lodNodes = #()

		if not FnGetLODNodes &lodNodes then
		(
			local errorMsg = "Failed to find/create lods!"
			messageBox errorMsg
			throw errorMsg
		)
		success += 1
		
		-- Model description
		
		DescribeModel (modelName.ModelGroupIDAsInt()) (modelName.ModelIDAsInt()) 
		success += 1
		
		-- Thumbnails
		curStep += 1
		if not (progressUpdate (100*curStep/numOfSteps)) then
		(
			success = -2
			throw "Cancelled by user"
		)
		
		FnRenderThumbnails()
		success += 1
	
		SaveThumbnails()
		success += 1

		-- Create and export all LOD shells
		local zoom
        
           AntialiasBackground true
           SetAALevel      gAALevel
           SetAADiameter   gAADiameter
           SetAABlur       gAABlur
            
		for zoom = 5 to 1 by -1 do 
		(
			local curShell = lodNodes[zoom] -- get lodNode for current zoom
			local rot

			modelName.zoom = zoom

			for rot = 1 to 4 do 
			(
				curStep += 1
				if not (progressUpdate (100*curStep/numOfSteps)) then
				(
					success = -2
					throw "Cancelled by user"
				)

				modelName.rot = rot

				-- We use a global object to pass view information to subroutines
				gCameraRig.SetCam zoom rot
				gCameraRig.SetupRenderViewport curShell

				local shellNode = FnCreateLowPolyShell curShell modelName doNight:bHasNightMats

				if (shellNode == undefined) then 		-- Create shell may fail
				(
					local errorMsg = ("Failed to create low poly shell!\n" + (FnZoomRotInfoString zoom rot))
					messageBox errorMsg
					throw errorMsg
				)
				
				success += 1
				
				--	Collect all nodes for export
				local nodesToExport
				
				if (shellNode.children.count > 0) then 
				(
					nodesToExport = (nodesToSave + shellNode.children)
				)
				else 
				(
					nodesToExport = (nodesToSave + #(shellNode))
				) 
				
				success += 1
				
				-- Need to rotate the node back to rest position so it will face the right way when pu in the game
				local nodeGroup
				
				if (nodesToSave.count > 0) then
				(
					nodeGroup = group nodesToSave 
					nodeGroup.pivot = (point3 0 0 0)
					rotate nodeGroup (-90*(rot-1)) (point3 0 0 1)				
				)
				
				shellNode.pivot = (point3 0 0 0)
				rotate shellNode(-90*(rot-1)) (point3 0 0 1)
				
				success += 1
				
				local saved = SaveS3DToDAT nodesToExport (modelName.ModelGUID())
				
				success += 1
				
				if (nodesToSave.count > 0) then
				(
					rotate nodeGroup (90*(rot-1)) (point3 0 0 1)
					ungroup nodeGroup
				)
				
				delete shellNode
				
				success += 1
				
				if (not saved) then
				(				
					local errorMsg = ("Failed to export S3D!\n" + (FnZoomRotInfoString zoom rot))
					messageBox errorMsg
					throw errorMsg
				)
				
				success += 1
			)
		)
			
		success = -1
	)
	catch()
	
	--prograssUpdate 100
	
	gc() 									-- do a garbage collect
	delete $TB2_*						-- delete any dangling temp meshes that might have been created before
	FnCleanupDestFolder destpath	-- Clean up destination folder
	-- resetMaxFile #noPrompt
		
	-- Delete oldfiles in temp folder
	FnDeleteFiles ((modelName.Destination which:#temp) + "*.*")
	
	gCameraRig.ResetRig()

	--	Report elapsed time
	local timeStr = ("Processing time was : " + (((timeStamp()-startTime)/1000.0) as string) + " seconds\n")
	fnLogMessage timeStr
	format timeStr to:listener

	ExportShutdown()
	progressEnd()
	setArrowCursor()
	
	if success == -1 then
	(
		InfoBox ("Export successful.\n" + timeStr)
	)
	else if success == -2 then	--	Progress cancelled by user
	(
		if datName != undefined then
		(
			FnDeleteFiles datName
		)
		
		InfoBox "Export aborted by user."
	)
	else
	(
		messageBox ("Export failed!\nCode = " + (success as string))
	)

	return success
)

----------------------------------------------------------------------------------------------------
-- Function:	FnDoPreviewRender 
--	Param:		night or day?
-- Param:		zoom 
-- Param:		rotate 
----------------------------------------------------------------------------------------------------
fn FnDoPreviewRender nightPrev zoom rotate = 
(
	if geometry.count == 0 then
	(
		return false
	)
	
	local success = false
	
	AntialiasBackground	true
	SetAALevel				gPreviewAALevel
	SetAADiameter			gAADiameter
	SetAABlur				gAABlur

	try
	(
		setWaitCursor()
		TextureMillStartup()
		
		if (not gCameraRig.SetCam zoom rotate) then
		(
			throw "Failed to set the camera"
		)

		--viewport.setCamera gCameraRig.curCam

		local meshName = "LOD" + (gCameraRig.curZoom as string)

		if (gCameraRig.curZoom < 3) then 
		(
			meshName = "LOD3"
		)

		local meshNode = FnGlobalFindNode meshName

		if (meshNode == undefined) then 
		(
			--local errorMsg = ("Couldn't find lod mesh : " + meshName)
			--FnErrorMessage errorMsg
			--throw errorMsg	
			FnCreateLOD_3_4_5()
			meshNode = FnGlobalFindNode meshName
		)

		if (meshNode == undefined) then 
		(
			local errorMsg = ("Couldn't find or create lod mesh : " + meshName)
			FnErrorMessage errorMsg
			throw errorMsg	
		)
		
		gCameraRig.SetupRenderViewport meshNode
		hide $LOD* 		-- it's a pain when LOD's accidentally show up in previews

		local dummyBox = gCameraRig.GetPreviewRegion meshNode
		
		--SetAALevel gAALevel

		if (nightPrev == #day) then
		(
			FnDoRender2 dummyBox false display:#day
		)
		else
		(
			FnDoRender2 dummyBox true display:#all
			ShowNightPreview kNigthColor ("Night preview. " + (FnZoomRotInfoString zoom rotate))
		)

		success = true
	)
	catch
	(
	)
	
	SetAALevel	gAALevel
		
	TextureMillShutdown false	--	false = do not auto close VFB
	
	setArrowCursor()

	return success	
)

----------------------------------------------------------------------------------------------------
-- Function:	FnDoThumbnailPreview 
----------------------------------------------------------------------------------------------------
fn FnDoThumbnailPreview = 
(
	if geometry.count == 0 then
	(
		return false
	)

	local success = false
	
	SetAALevel 		gPreviewAALevel
	--SetAADiameter	gAADiameter
	--SetAABlur		gAABlur

	try
	(
		setWaitCursor()
		
		TextureMillStartup()

		local meshName = "LOD3"
		local meshNode = FnGlobalFindNode meshName

		if (meshNode == undefined) then 
		(
			FnCreateLOD_3_4_5()
			meshNode = FnGlobalFindNode meshName
		)

		if (meshNode == undefined) then 
		(
			local errorMsg = ("Couldn't find or create lod mesh : " + meshName)
			FnErrorMessage errorMsg
			throw errorMsg	
		)
		
		success = FnRenderThumbnails()
	)
	catch
	(
	)
	
	SetAALevel	gAALevel

	TextureMillShutdown false	--	false = do not auto close VFB
	setArrowCursor()

	return success	
)
