#
# Muvizu-ASE-Exporter.rb
# 2010/09/18 - RUBY
#
#-------------------------------------------------------------------------------------------
#
# This program (plugin) is based on original work by Raphael Couturier (HardPCM). It has been
# modified, under the terms of the GNU Lesser General Public License, all changes made have
# been with a goal in mind; optimising the original ASE export feature from HardPCM for use
# with Muvizu (http://www.muvizu.com).
#
#-------------------------------------------------------------------------------------------
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA, or go to
# http://www.gnu.org/copyleft/lesser.txt.
#
#-------------------------------------------------------------------------------------------
#
# Original Auther - Raphael Couturier (HardPCM) - hardpcm666@hotmail.com, hardpcm666@gmail.com
# Updated by - Jamie Hill (CerebralDump) - jamie@muvizu.com
#
# File: MuvizuASEExporter.rb
# Version: 0.0.8
# Started: 01/09/10
# Released: -
# Langage: English
#
# Description: Main Source for a Google Sketchup Plugin that allows you to export
#                   ASE files specifically for Muvizu, which uses the Unreal 3 games engine.
#
#-------------------------------------------------------------------------------------------
#
# Special thanks to:
#
#   - Raphael Couturier (HardPCM) for the original work.
#
#   - Jim Skivington for testing the early attempts and the changes. 
#
#-------------------------------------------------------------------------------------------
# Change List / History
#
# 0.0.1 - Write out texture filename and path for ASE submaterial tag *BITMAP "..."
# 0.0.2 - Generate collision mesh automatically. Copy geometry into another GEOMODEL object prefixing object name with UCX. Collision was too complex.
# 0.0.3 - Removed non-essential parts for Muvizu input (import, and all export except ASE functions).
# 0.0.4 - Limited output to components only. Remove all handling of groups.
# 0.0.5 - UI to allow selection of one component as geometry and one component as collision.
# 0.0.6 - Renamed classes and methods to avoid dependency or interfearance with the original plugin.
# 0.0.7 - Refined UI to reduce and report errors.
# 0.0.8 - Updated about text and source comments for completeness.
#
#-------------------------------------------------------------------------------------------

require 'sketchup.rb'

class WalkerWaitT3D_UTX

# Updated: by HardPCM date unknown.
	def initialize
		@m_nIndex = 0
		@m_sTable = ["|","/","-","\\","|","/","-","\\"]
	end

# Updated: by HardPCM date unknown.
	def Tick(nLevel = 1)
    # Show step
		a_sText = "Processing at level No = #{"%0.f" %(nLevel)}       \"O.{#{@m_sTable[@m_nIndex]}}.O\" \n"
		code = Sketchup.set_status_text a_sText
    
		# Next step
		@m_nIndex += 1
		if @m_nIndex >= 8
			@m_nIndex = 0;
		end
	end

# Updated: by HardPCM date unknown.
	def Say(sMessage)
		code = Sketchup.set_status_text sMessage
	end
end


class IteratorerDataT3D_UTX

# Updated: 24-September-2010
	def initialize(pFirstNode = nil, pProcessorNode = nil, bFirstNode = nil, bProcessorNode = nil, thePath = "")
    # Populate the instance variables to process the sketchup model into ASE format.
		@m_pProcessorNodeCol = pProcessorNode
		@m_pFirstNode = pFirstNode
    
    # Expect to have collision by default.
    @m_hasCollision = true
    
    # Get the names of the components exported for later reporting.
    @m_theModelName = pFirstNode.definition.name
    @m_theSavePath = thePath
    
    # Check if there is collision geometry to be generated as well.
    if bFirstNode != nil
      @m_bProcessorNodeCol = bProcessorNode
      @m_bFirstNode = bFirstNode
      @m_theCollisionName = bFirstNode.definition.name
    else
      # No collision.
      @m_hasCollision = false
    end
    
    # Prepare the material lists.
		@m_pMaterials = []
		@m_pMaterials.push(nil)
		@m_bMaterialsChanged = false
    
    # Prepare transforms - not sure why anything needs to be transformed...
		@m_Transformations = []
		@m_Transformations.push(Geom::Transformation.new)
    
    # Set level to begin processing at.
		@m_nLevel = 1
    
    # Start the wait walker to update the status bar text.
		@m_pWalker = WalkerWaitT3D_UTX.new
	end

# Updated: 24-September-2010
	def Start()
    # Process the geometry model.
		self.Process(@m_pFirstNode, @m_pFirstNode, @m_pProcessorNodeCol)
    
    if @m_hasCollision == true
      # If selected process the collision model.
      self.Process(@m_bFirstNode, @m_bFirstNode, @m_bProcessorNodeCol)
    end
    
    # Write to file.
		@m_pProcessorNodeCol.WriteHeader
    @m_pProcessorNodeCol.WriteMaterials
    @m_pProcessorNodeCol.WriteGeometry
    if @m_hasCollision == true
      @m_bProcessorNodeCol.WriteCollision
    end
    
    # Finished!
		@m_pWalker.Say("Job Done - Model exported!")
    theEndMessage = ""
    if @m_hasCollision == true
      theEndMessage = "Collision geometry exported from component #{"%s" %(@m_theCollisionName)}\n\n"
    else
      theEndMessage = "No collision geometry exported. Muvizu will create this on import.\n\n"
    end
    UI.messagebox "#{"%s" %(theEndMessage)} Geometry model exported from component #{"%s" %(@m_theModelName)}\n\nFile saved to #{"%s" %(@m_theSavePath)}"
	end

# Updated: 24-September-2010
	def Process(pCurrentNode, pParent, theProcessor)

    # Explode components or groups so we can process each face within it.
		if(pCurrentNode.class == Sketchup::Group)
			pCurrentNode = pCurrentNode.entities 
		elsif(pCurrentNode.class == Sketchup::ComponentInstance)
			pCurrentNode = pCurrentNode.definition.entities
    end
    
    for a_Entity in pCurrentNode
      if(a_Entity.class == Sketchup::Face and a_Entity.layer.visible?)
        # Identify the material on the face being examined.
        a_pMaterial = a_Entity.material
        @m_bMaterialsChanged = false
        if a_pMaterial != nil
          @m_pMaterials.push(a_pMaterial)
          @m_bMaterialsChanged = true
        end
        
        # Process the mesh co-ordinates for the face being examined.
        theProcessor.ProcessFace(a_Entity, @m_pMaterials.last, @m_Transformations.last)
          
        # Finish processing the material on the face being examined.
        if @m_bMaterialsChanged == true
					@m_pMaterials.pop
          @m_bMaterialsChanged = false
        end
          
        # Reporting only.
        @m_pWalker.Tick(@m_nLevel)
      end
    end
      
    for a_Entity in pCurrentNode
     # If there is a component within the current component or group then recursively process that component as well.
     if((a_Entity.class == Sketchup::ComponentInstance or a_Entity.class == Sketchup::Group) and a_Entity.layer.visible?)
        @m_nLevel += 1
        @m_Transformations.push(@m_Transformations.last * a_Entity.transformation)
        self.Process(a_Entity, pCurrentNode, theProcessor)
        @m_Transformations.pop
        @m_nLevel -= 1
      end
    end
  end
end

class FileWritter

# Updated: 24-September-2010
  def initialize(asePath)
    @m_pFile = nil
		@m_sAsePath = asePath
    self.OpenFile
  end

# Updated: 24-September-2010
	def OpenFile
		@m_pFile = File.new(@m_sAsePath,"wb")
	end
  
# Updated: 24-September-2010
  def WriteToFile(aLine)
    @m_pFile.write(aLine)
    @m_pFile.flush
  end

# Updated: 24-September-2010
  def CloseFile
    @m_pFile.flush
		@m_pFile.close
  end
end
  
class ExporterDataASE_UTX

# Updated: 24-September-2010
	def initialize(theFileWritter, theModelName)
    @m_FileWritter = theFileWritter
    
    @m_vVertex = []
		@m_vVertex.fill([0.0,0.0,0.0],0,15000)
		@m_nNextVertex = 0
    
    @m_vVertexUV = []
		@m_vVertexUV.fill([0.0,0.0],0,15000)
		@m_nNextVertexUV = 0

		@m_vNormal = []
		@m_vNormal.fill([0.0,0.0,0.0],0,15000)
		@m_nNextNormal = 0

		@m_pTriangle = []
		@m_pTriangle.fill([0,0,0,1,1,1],0,15000)
		@m_nNextTriangle = 0

		@m_pMaterial = []
		@m_pMaterial.fill([""],0,1000)
		@m_nNextMaterial = 0
    
    @m_nModelName = theModelName
	end
  

# Updated: by HardPCM date unknown.
	def NextVertexXYZ(pPacket)
		# Search an existing XYZ.
		a_nIndex = @m_nNextVertex - 1
		a_nCount = 256
    
		if a_nCount > a_nIndex + 1
			a_nCount = a_nIndex + 1
		end
    
		while a_nCount > -1
      a_pTemp = @m_vVertex[a_nIndex]
			if (a_pTemp[0] == pPacket[0]) && (a_pTemp[1] == pPacket[1]) && (a_pTemp[2] == pPacket[2])
        if (a_pTemp[3] == pPacket[3]) && (a_pTemp[4] == pPacket[4]) && (a_pTemp[5] == pPacket[5])
					return a_nIndex
				end
			end
			a_nIndex = a_nIndex - 1
			a_nCount = a_nCount - 1
		end
    
		# Create a new XYZ
		@m_vVertex[@m_nNextVertex] = pPacket
		@m_nNextVertex = @m_nNextVertex + 1
		return @m_nNextVertex - 1
	end
# Updated: by HardPCM date unknown.
	def NextVertexUV(pPacket)
		# Search an existing UV
		a_nIndex = @m_nNextVertexUV - 1
		a_nCount = 256
    
		if a_nCount > a_nIndex + 1
			a_nCount = a_nIndex + 1
		end
    
		while a_nCount > -1
			a_pTemp = @m_vVertexUV[a_nIndex]
			if (a_pTemp[0] == pPacket[0]) && (a_pTemp[1] == pPacket[1]) && (a_pTemp[2] == pPacket[2])
				return a_nIndex
			end
			a_nIndex = a_nIndex - 1
			a_nCount = a_nCount - 1
		end
    
		# Create a new UV
		@m_vVertexUV[@m_nNextVertexUV] = pPacket
		@m_nNextVertexUV = @m_nNextVertexUV + 1
		return @m_nNextVertexUV - 1
	end

# Updated: by HardPCM date unknown.
	def NextNormalXYZ(pPacket)
    @m_vNormal[@m_nNextNormal] = pPacket
		@m_nNextNormal = @m_nNextNormal + 1
		return @m_nNextNormal - 1
	end

# Updated: by HardPCM date unknown.
	def NextFaceABC(pPacket)
		@m_pTriangle[@m_nNextTriangle] = pPacket
		@m_nNextTriangle = @m_nNextTriangle + 1
		return @m_nNextTriangle - 1
	end


# Updated: by HardPCM date unknown.
	def NextMaterial(pPacket)
		# Search an existing material
		a_nIndex = @m_nNextMaterial - 1
		while a_nIndex > -1
			a_pTempPacket = @m_pMaterial[a_nIndex]
			if a_pTempPacket[0] == pPacket[0]
				return a_nIndex
			end
			a_nIndex = a_nIndex - 1
		end
    
		# Create a new material
		@m_pMaterial[@m_nNextMaterial] = pPacket
		@m_nNextMaterial = @m_nNextMaterial + 1
		return @m_nNextMaterial - 1
	end

# Updated: by HardPCM date unknown.
	def VectorFromSketchup(pVector)
		return [pVector.x, pVector.y, pVector.z]
	end

# Updated: by HardPCM date unknown.
	def VectorUvFromSketchup(pVector)
		return [pVector.x, pVector.y]
	end
# Updated: 24-September-2010
	def WriteHeader
		@m_FileWritter.WriteToFile("*3DSMAX_ASCIIEXPORT\r\n")
		@m_FileWritter.WriteToFile("*COMMENT Muvizu ASE Exporter.\r\n")
    @m_FileWritter.WriteToFile("*COMMENT Based on HardPCM'S ASE output plugin for Sketchup (HSKP2UNR.rb)\r\n")
	end

#Updated: 24-September-2010
	def WriteMaterials
    
    # Write the material header.
		@m_FileWritter.WriteToFile("*MATERIAL_LIST {\r\n")
		@m_FileWritter.WriteToFile("*MATERIAL_COUNT 1\r\n")
		@m_FileWritter.WriteToFile("	*MATERIAL 0 {\r\n")
		@m_FileWritter.WriteToFile("		*MATERIAL_NAME \"MUVIZU_ASE_TEXTURE\"\r\n")
		@m_FileWritter.WriteToFile("		*MATERIAL_CLASS \"Multi/Sub-Object\"\r\n")
    
    # Write the number of sub-materials to expect.
		@m_FileWritter.WriteToFile("		*NUMSUBMTLS #{"%i" %(@m_nNextMaterial)}\r\n")
    
    # Output sub-material list.
		a_nIndex = 0
		a_nCount = @m_nNextMaterial
    while (a_nIndex < a_nCount)
      
			a_pPacket = @m_pMaterial[a_nIndex]
      
			@m_FileWritter.WriteToFile("		*SUBMATERIAL #{"%i" %(a_nIndex)} {\r\n");
			@m_FileWritter.WriteToFile("			*MATERIAL_NAME \"#{"%s" %(a_pPacket[0])}\"\r\n");
			@m_FileWritter.WriteToFile("			*MATERIAL_CLASS \"Standard\"\r\n");
			@m_FileWritter.WriteToFile("			*MAP_DIFFUSE {\r\n");
			@m_FileWritter.WriteToFile("				*MAP_CLASS \"Bitmap\"\r\n");
			@m_FileWritter.WriteToFile("				*BITMAP \"#{"%s" %(a_pPacket[2])}\"\r\n");
			@m_FileWritter.WriteToFile("				*UVW_U_OFFSET 0.0\r\n");
			@m_FileWritter.WriteToFile("				*UVW_V_OFFSET 0.0\r\n");
			@m_FileWritter.WriteToFile("				*UVW_U_TILING 1.0\r\n");
			@m_FileWritter.WriteToFile("				*UVW_V_TILING 1.0\r\n");
			@m_FileWritter.WriteToFile("			}\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("	}\r\n")
		@m_FileWritter.WriteToFile("}\r\n")
	end

# Updated: 24-September-2010
	def WriteGeometryBegin
		@m_FileWritter.WriteToFile("*GEOMOBJECT {\r\n")
		@m_FileWritter.WriteToFile("	*NODE_NAME \"#{"%s" %(@m_nModelName)}\"\r\n")
		@m_FileWritter.WriteToFile("	*NODE_TM {\r\n")
		@m_FileWritter.WriteToFile("	*NODE_NAME \"#{"%s" %(@m_nModelName)}\"\r\n")
		@m_FileWritter.WriteToFile("	}\r\n")
		@m_FileWritter.WriteToFile("	*MESH {\r\n")
		@m_FileWritter.WriteToFile("		*TIMEVALUE 0\r\n")
	end

# Updated: 24-September-2010
	def WriteGeometryColBegin
		@m_FileWritter.WriteToFile("*GEOMOBJECT {\r\n")
		@m_FileWritter.WriteToFile("	*NODE_NAME \"UCX_#{"%s" %(@m_nModelName)}\"\r\n")
		@m_FileWritter.WriteToFile("	*NODE_TM {\r\n")
		@m_FileWritter.WriteToFile("	*NODE_NAME \"UCX_#{"%s" %(@m_nModelName)}\"\r\n")
		@m_FileWritter.WriteToFile("	}\r\n")
		@m_FileWritter.WriteToFile("	*MESH {\r\n")
		@m_FileWritter.WriteToFile("		*TIMEVALUE 0\r\n")
	end

# Updated: 24-September-2010
	def WriteVertexXYZ
		@m_FileWritter.WriteToFile("		*MESH_NUMVERTEX #{"%i" %(@m_nNextVertex)}\r\n")
		@m_FileWritter.WriteToFile("			*MESH_NUMFACES #{"%i" %(@m_nNextTriangle)}\r\n")
		@m_FileWritter.WriteToFile("		*MESH_VERTEX_LIST {\r\n")
    
		a_nIndex = 0
		a_nCount = @m_nNextVertex
		while (a_nIndex < a_nCount)
			a_pPacket = @m_vVertex[a_nIndex]
			@m_FileWritter.WriteToFile("			*MESH_VERTEX    #{"%i" %(a_nIndex)}	#{"%+013.6f" %(a_pPacket[0])}	#{"%+013.6f" %(a_pPacket[1])}	#{"%+013.6f" %(a_pPacket[2])}\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("		}\r\n")
    
	end

# Updated: 24-September-2010
	def WriteFaceVertexXYZ
		@m_FileWritter.WriteToFile("		*MESH_FACE_LIST {\r\n")
    
		a_nIndex = 0
		a_nCount = @m_nNextTriangle
		while (a_nIndex < a_nCount)
			a_pPacket = @m_pTriangle[a_nIndex]
			@m_FileWritter.WriteToFile("			*MESH_FACE    #{"%i" %(a_nIndex)}: A:    #{"%i" %(a_pPacket[0])} B:    #{"%i" %(a_pPacket[1])} C:    #{"%i" %(a_pPacket[2])}	 AB:	1	BC:	1	CA:	1	*MESH_SMOOTHING 0 	*MESH_MTLID #{"%i" %(a_pPacket[6])}\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("		}\r\n")
	end
  
# Updated: 24-September-2010
	def WriteVertexUV
    @m_FileWritter.WriteToFile("		*MESH_NUMTVERTEX #{"%i" %(@m_nNextVertexUV)}\r\n")
		@m_FileWritter.WriteToFile("		*MESH_TVERTLIST {\r\n")
    
    a_nIndex = 0
		a_nCount = @m_nNextVertexUV
		while (a_nIndex < a_nCount)
      a_pPacket = @m_vVertexUV[a_nIndex]
			@m_FileWritter.WriteToFile("			*MESH_TVERT #{"%i" %(a_nIndex)}	#{"%+013.6f" %(a_pPacket[0])}	#{"%+013.6f" %(a_pPacket[1])}	0.000000\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("		}\r\n")
  end

# Updated: 24-September-2010
	def WriteFaceVertexUV
		@m_FileWritter.WriteToFile("		*MESH_NUMTVFACES #{"%i" %(@m_nNextTriangle)}\r\n")
		@m_FileWritter.WriteToFile("		*MESH_TFACELIST {\r\n")
    
		a_nIndex = 0
		a_nCount = @m_nNextTriangle
		while (a_nIndex < a_nCount)
			a_pPacket = @m_pTriangle[a_nIndex]
			@m_FileWritter.WriteToFile("			*MESH_TFACE #{"%i" %(a_nIndex)}	#{"%i" %(a_pPacket[3])}	#{"%i" %(a_pPacket[4])}	#{"%i" %(a_pPacket[5])}\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("		}\r\n")
  end

# Updated: 24-September-2010
	def WriteFaceNormals
		@m_FileWritter.WriteToFile("		*MESH_NORMALS {\r\n")
    
		a_nIndex = 0
		a_nCount = @m_nNextTriangle
		while (a_nIndex < a_nCount)
			a_pPacket = @m_pTriangle[a_nIndex]
			@m_FileWritter.WriteToFile("			*MESH_FACENORMAL #{"%i" %(a_nIndex)}    #{"%+013.6f" %(a_pPacket[7])}    #{"%+013.6f" %(a_pPacket[8])}    #{"%+013.6f" %(a_pPacket[9])}\r\n");
			a_nID = a_pPacket[0]
			a_pTP = @m_vVertex[a_nID]
			@m_FileWritter.WriteToFile("				*MESH_VERTEXNORMAL #{"%i" %(a_nID)}	#{"%+013.6f" %(a_pTP[3])}	#{"%+013.6f" %(a_pTP[4])}	#{"%+013.6f" %(a_pTP[5])}\r\n");
			a_nID = a_pPacket[1]
			a_pTP = @m_vVertex[a_nID]
			@m_FileWritter.WriteToFile("				*MESH_VERTEXNORMAL #{"%i" %(a_nID)}	#{"%+013.6f" %(a_pTP[3])}	#{"%+013.6f" %(a_pTP[4])}	#{"%+013.6f" %(a_pTP[5])}\r\n");
			a_nID = a_pPacket[2]
			a_pTP = @m_vVertex[a_nID]
			@m_FileWritter.WriteToFile("				*MESH_VERTEXNORMAL #{"%i" %(a_nID)}	#{"%+013.6f" %(a_pTP[3])}	#{"%+013.6f" %(a_pTP[4])}	#{"%+013.6f" %(a_pTP[5])}\r\n");
			a_nIndex = a_nIndex + 1
		end
    
		@m_FileWritter.WriteToFile("		}\r\n")
	end

# Updated: 24-September-2010
	def WriteGeometryFinish
		@m_FileWritter.WriteToFile("	}\r\n")
		@m_FileWritter.WriteToFile("	*MATERIAL_REF 0\r\n")
		@m_FileWritter.WriteToFile("}\r\n")
	end

#Updated 24-September-2010
	def ProcessFace(pFace, pMaterial, pTransformations)

		# Extract the mesh.
		a_pMesh = pFace.mesh 7
    
		# Transform the mesh.
		a_pMesh.transform! pTransformations
    
		# Eextract material info.
		a_nMaterialU = 1.0
		a_nMaterialV = 1.0
    # Load default material name and bmp file path. Note the file toto.bmp
    # does not exist unless you create it. For Muvizu it is recommended to
    # create this file for the best results.
    a_sTexture = "Engine.DefaultTexture"
    a_pTexturePath = "C:\\ut3\\toto.bmp"
    
    # Make sure that the material reference exists before processing anything else.
		if pMaterial != nil
      
      # If the material has a name in Sketchup use that instead of the default.
			if pMaterial.name != ""
        a_sTexture = pMaterial.name
      end if
      
      # Check that the material has a texture (and is not just a colour) before ensuring that
      # the texture filename reference exists and has content. Use the actual path and filename
      # for that texture / material instead of the default toto.bmp
      if pMaterial.texture != nil
        if pMaterial.texture.filename != nil
          if pMaterial.texture.filename != ""
            a_pTexturePath = pMaterial.texture.filename
          end
        end
      end
      
      # Take a reference to the texture object as well.
      a_pTexture = pMaterial.texture
    end

    # Pack the material for later writting to file.
    a_pMatPack = [a_sTexture, pMaterial, a_pTexturePath]
    a_nMaterialID = self.NextMaterial(a_pMatPack)
      
    # Extract the triangle normal.
    a_vNormal = pFace.normal 
    a_nNx = a_vNormal.x
    a_nNy = a_vNormal.y
    a_nNz = a_vNormal.z
      
    # Loop for each triangle in the face.
    a_nIndex = 1
    while (a_nIndex <= a_pMesh.count_polygons)
        
      # Extract the triangle indexes.
      a_pPolygon = a_pMesh.polygon_at a_nIndex
      
      # Extract the triangle vertex.
      a_pVa = self.VectorFromSketchup(a_pMesh.point_at a_pPolygon[0])
      a_pVb = self.VectorFromSketchup(a_pMesh.point_at a_pPolygon[1])
      a_pVc = self.VectorFromSketchup(a_pMesh.point_at a_pPolygon[2])
        
      a_pVa[3] = a_nNx
      a_pVa[4] = a_nNy
      a_pVa[5] = a_nNz
       
      a_pVb[3] = a_nNx
      a_pVb[4] = a_nNy
      a_pVb[5] = a_nNz
        
      a_pVc[3] = a_nNx
      a_pVc[4] = a_nNy
      a_pVc[5] = a_nNz
        
      a_nVa = self.NextVertexXYZ(a_pVa)
      a_nVb = self.NextVertexXYZ(a_pVb)
      a_nVc = self.NextVertexXYZ(a_pVc)
        
      # Extract the texture coordinates.
      a_pTa = self.VectorUvFromSketchup(a_pMesh.uv_at a_pPolygon[0],a_pPolygon[0])
      a_pTb = self.VectorUvFromSketchup(a_pMesh.uv_at a_pPolygon[1],a_pPolygon[1])
      a_pTc = self.VectorUvFromSketchup(a_pMesh.uv_at a_pPolygon[2],a_pPolygon[2])
      a_nTa = self.NextVertexUV(a_pTa)
      a_nTb = self.NextVertexUV(a_pTb)
      a_nTc = self.NextVertexUV(a_pTc)			
        
      # Convert to face,
      a_pFace = [a_nVa, a_nVb, a_nVc, a_nTa, a_nTb, a_nTc, a_nMaterialID, a_nNx, a_nNy, a_nNz]
      a_nFace = NextFaceABC(a_pFace)
      # --- next triangle ------------------------------------------------
      a_nIndex += 1
    end
	end

# Updated 24-September-2010
  def WriteGeometry
    # Write out all parts for the geometry model.
    self.WriteGeometryBegin
		self.WriteVertexXYZ
		self.WriteFaceVertexXYZ
		self.WriteVertexUV
		self.WriteFaceVertexUV
		self.WriteFaceNormals
		self.WriteGeometryFinish
  end

# Updated 24-September-2010
  def WriteCollision
    # The only difference between WriteCollision and WriteGeometry is that the method
    # WriteGeometryColBegin is called, which does the same as WriteGeometryBegin but
    # prefixes the name of the object with UCX for a collision object.
    self.WriteGeometryColBegin
		self.WriteVertexXYZ
		self.WriteFaceVertexXYZ
		self.WriteVertexUV
		self.WriteFaceVertexUV
		self.WriteFaceNormals
		self.WriteGeometryFinish    
  end

end

class MuvizuASEExporter
   
   #Updated: 24-September-2010
	def About
    
    UI.messagebox("Muvizu ASE Exporter

Original work by Raphael Couturier (HardPCM) with HSKP2UNR
HardPCM's Google Sketchup Exporter to T3D/PSK/ASE/OBJ format
for UT3/UT2K4/UT2K3/UT99 game engines. Version 0.9.8 May 26nd 2009
was used as the foundation. The aim of this update is to provide
a more optomised exporter for use with Muvizu (http://www.muvizu.com)

Change List / History

# 0.0.1 - Write out texture filename and path for ASE submaterial tag *BITMAP "..."
# 0.0.2 - Generate collision mesh automatically. Copy geometry into another GEOMODEL object prefixing object name with UCX. Collision was too complex.
# 0.0.3 - Removed non-essential parts for Muvizu input (import, and all export except ASE functions).
# 0.0.4 - Limited output to components only. Remove all handling of groups.
# 0.0.5 - UI to allow selection of one component as geometry and one component as collision.
# 0.0.6 - Renamed classes and methods to avoid dependency or interfearance with the original plugin.
# 0.0.7 - Refined UI to reduce and report errors.
# 0.0.8 - Updated about text and source comments for completeness.

Freeware under the GNU Lesser General Public License version 2 or later.

 Special thanks to:

   - Raphael Couturier (HardPCM) for the original work.

   - Jim Skivington for testing the early attempts at the changes. 

 How to install:

   filename is: \"MuvizuASEExporter.rb\"
   and put it in the directory like
   \"C:\Program Files\Google\Google SketchUp X\Plugins\"

 How this works:

   Everything is in inch OK!!!
   So 512 inch unit = 512 unreal unit.
   This apply to the model size and the material size.

   Muvizu supports both single and double sided objects. This means you do not need to worry about WHITE or BLUE mode for faces, however to achieve the best results from modelling for a games engine it is recommended that use only use signle sided objects. The recommendation is for WHITE (when all faces of your object look WHITE on the outside). If you do not then some faces will only be visible inside your object. Each face needs to have a texture applied to it.

  http://www.muvizu.com/
  
  " , MB_MULTILINE , "Muvizu ASE Exporter")
    
	end

# Updated: 24-September-2010
  def CountComponents deffs = nil
    count = 0
    if deffs != nil
      deffs.each {|x|
        if x.is_a? Sketchup::ComponentInstance         
          count += 1
       end
      }
    end
    # Returns the total number of available components.
    return count
  end
  
# Updated: 24-September-2010
  def GetFirstComponent deffs = nil
    hasFirst = false
    first = ""
    if deffs != nil
      deffs.each {|modelref|
      if modelref.is_a? Sketchup::ComponentInstance
      # Get the name of the first component for default selection on the Export Options dialog box.
        if hasFirst == false
          deff = modelref.definition
          first = deff.name
          hasFirst = true
        end
      end
      }
    end
    return first
  end

# Updated: 24-September-2010
  def GetDropDownList deffs = nil
    listGeometry = ""
    if deffs != nil
      deffs.each {|x|
        if x.is_a? Sketchup::ComponentInstance
          # Go through all components that exist in the sketchup model and append each ones name to a string for use in a combo box.
          deff = x.definition
          listGeometry += "#{"%s" %(deff.name)}|"
       end
      }
    end
    return listGeometry
  end

# Updated: 24-September-2010
  def GetDropDownListCollision  deffs = nil
    # Always return the "None" option.
    listCollision = "None - Muvizu generated collision"
    if deffs != nil
      # Append the None option to the list generated of existing components.
      listCollision += "|#{"%s" %(self.GetDropDownList deffs)}"
    end
    return listCollision
  end
  
  # Updated: 24-September-2010
  def GetEntityRefByName deffs = nil, theName = ""
    # Return nil on error.
    modelentity = nil
    # Make sure we have valid references.
    if deffs != nil
      deffs.each {|modelref|
      if modelref.is_a? Sketchup::ComponentInstance
        deff = modelref.definition
        # Go through all entities, check that it is a ComponentInstance and return reference to the one that matches the provided name,
        if deff.name == theName
          modelentity = modelref
        end
      end
      }  
    end
    return modelentity
  end
  
def getMaterialFileNames(theMaterials)
    allTextures = ""
    theMaterials.texture.each { | text |
      if theMaterials.texture != nil
        allTexture += "#{"%s" %(theMaterials.texture.filename)}|"
      end
    }
    return allTextures
  end
  
    def findDuplicate(arrayMaterials, check)
      foundDup = false
      arrayMaterials.each { | material | 
        if material.texture.filename == check.texture.filename
          foundDup = true
        end
      }
      return foundDup
    end
  
  	def ProcessForTextures(pCurrentNode, pParent)
      
      #tmpTextures = Array.new
      # Explode components or groups so we can process each face within it.
		if(pCurrentNode.class == Sketchup::Group)
			pCurrentNode = pCurrentNode.entities 
		elsif(pCurrentNode.class == Sketchup::ComponentInstance)
			pCurrentNode = pCurrentNode.definition.entities
    end
    
    for a_Entity in pCurrentNode
      #UI.messagebox "Processing entities"
      if(a_Entity.class == Sketchup::Face and a_Entity.layer.visible?)
        # Identify the material on the face being examined.
        a_pMaterial = a_Entity.material
        #UI.messagebox "Checking materials"
        if self.findDuplicate(@allMaterials, a_pMaterial) == false
          @allMaterials.push(a_pMaterial)
          #tmpTextures += "#{"%s" %(a_pMaterial.texture.filename)}|"
          #UI.messagebox "#{"%s" %(a_pMaterial.texture.filename)}|"
        end
      end
    end
      
    for a_Entity in pCurrentNode
     # If there is a component within the current component or group then recursively process that component as well.
     if((a_Entity.class == Sketchup::ComponentInstance or a_Entity.class == Sketchup::Group) and a_Entity.layer.visible?)
        self.ProcessForTextures(a_Entity, pCurrentNode)
      end
    end
  end
  
  # Updated: 24-September-2010
  def ExportOptionMenu(firstName, geoList, colList)
    a_pMenuResult = Array.new
    # Check there is at least one component to list in the export options menu.
    if geoList != nil && geoList.length > 0
      # Prepare the export options menu. 
      a_pMenuDropdowns = ["",  geoList, colList]
      a_pMenuPrompts = ["Name", "Model","Collision"]
      # Display Export Options
      a_pMenuResult = UI.inputbox a_pMenuPrompts, [firstName, firstName, firstName], a_pMenuDropdowns, "Export options"
      
      # Only progress if the user has selected Ok (true)
      if a_pMenuResult != false
        if a_pMenuResult[1] == a_pMenuResult[2]
          a_pMenuResult = Array.new
          a_pMenuResult[4] = -1
        end
      end
    else
      a_pMenuResult[4] = -2
    end
    return a_pMenuResult
  end
  
  # Updated: 24-September-2010
	def ExportASE
    # Get the entities available in the current model
    model = Sketchup.active_model
	  deffs = model.entities
    
    # Initialise variables for the menu and identifying the model and collision to export.
    a_pMenuA = ""
    a_pMenuB = ""
    first = ""
    exportCollision = true

    # Get the menu options.
    first = self.GetFirstComponent deffs
    a_pMenuA = self.GetDropDownList deffs
    a_pMenuB = self.GetDropDownListCollision deffs
    
    # Validation flags.
    readyToExport = false 
    progressExport = true
    continueResult = 0
    
    while readyToExport == false
      a_pMenuResult = self.ExportOptionMenu(first, a_pMenuA, a_pMenuB)
      if a_pMenuResult != false
        if a_pMenuResult[4] == -2
          UI.messagebox "There are no components in your sketchup model to export. Select the groups, faces or edges you wish to export then right click and do \"Make Component\""
          readyToExport = true
          progressExport = false
        elsif a_pMenuResult[4] == -1
          continueResult = UI.messagebox ("You have selected the same component for the geometry mesh and the collision mesh. This is not recommended, do you wish to progress?", MB_YESNO, "Are you sure?")
          if continueResult == 6
            readyToExport = true
            progressExport = true
          end
        else
          readyToExport = true
        end
      else
        readyToExport = true
        progressExport = false
      end
    end
    
    # Begin export if all options are ready.
    if progressExport == true
      modelname = a_pMenuResult[0]
      modelindex = a_pMenuResult[1]
      collisionindex = a_pMenuResult[2]

      # Check if collision is to be exported or not.
      if collisionindex == "None - Muvizu generated collision"
        exportCollision = false
      end
            
      # Prepare save path.
      a_sExportPath = UI.savepanel("Export Model to ASE", "" , "#{"%s" %(modelname)}.ase")
       
      # Only progress to generating the ASE file if a save path and name have been selected.
      if a_sExportPath != nil
         
        # Find a reference to the object that contains the model and collsion entities.
        modelentity = Sketchup::Entity
        collisionentity = Sketchup::Entity
        modelentity = self.GetEntityRefByName(deffs, modelindex)

        # Only get the collision entity ref if collision is to be generated.
        if exportCollision == true
          collisionentity = self.GetEntityRefByName(deffs, collisionindex)
        else
          collisionentity = nil
        end
        
        # Create the file handle. File handle is opened within initialize here.
        aFileWritter = FileWritter.new a_sExportPath
        
        # Prepare the data processors that will convert the Sketchup model into ASE format.
        a_pProcessor = ExporterDataASE_UTX.new(aFileWritter, modelname)
        
        # Only create a data processor if collision is to be generated.
        if exportCollision == true
          b_pProcessor = ExporterDataASE_UTX.new(aFileWritter, modelname)
        else
          b_pProcessor = nil
        end
              
        # Finally send the processors and entity references to the iterator to generate the final output ASE file.
        a_pIteratorer = IteratorerDataT3D_UTX.new(modelentity, a_pProcessor, collisionentity, b_pProcessor, a_sExportPath)
        a_pIteratorer.Start
        
        # Close the file handle.
        aFileWritter.CloseFile
      else
        UI.messagebox "No save to path or filename specified for export. Operation cancelled."
      end
    end
  end
end

file_loaded(".rb")

if(not file_loaded?("MuvizuASEExporter.rb"))
  MZAE = MuvizuASEExporter.new
	pMenuA = UI.menu("Plugins").add_submenu("Muvizu ASE Exporter")
	pMenuA.add_item("Export ASE Format")		{ (MZAE.ExportASE) }
  pMenuA.add_item("About Muvizu ASE Exporter")	{ (MZAE.About) }
else
  UI.messagebox "There was an error loading the Muvizu ASE Exporter."
end