# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation, either version 3
#  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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#  All rights reserved.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

from math import radians

import bpy
from mathutils import *


class DirectXExporter:
    def __init__(self, Config, context):
        self.Config = Config
        self.context = context

        self.Log("Begin verbose logging ----------\n")

        self.File = File(self.Config.filepath)

        self.Log("Setting up coordinate system...")
        # SystemMatrix converts from right-handed, z-up to left-handed, y-up
        self.SystemMatrix = (Matrix.Scale(-1, 4, Vector((0, 0, 1))) *
            Matrix.Rotation(radians(-90), 4, 'X'))
        self.Log("Done")

        self.Log("Generating object lists for export...")
        if self.Config.SelectedOnly:
            ExportList = list(self.context.selected_objects)
        else:
            ExportList = list(self.context.scene.objects)

        # ExportMap maps Blender objects to ExportObjects
        ExportMap = {}
        for Object in ExportList:
            if Object.type == 'EMPTY':
                ExportMap[Object] = EmptyExportObject(self.Config, self, Object)
            elif Object.type == 'MESH':
                ExportMap[Object] = MeshExportObject(self.Config, self,
                    Object)
            elif Object.type == 'ARMATURE':
                ExportMap[Object] = ArmatureExportObject(self.Config, self,
                    Object)

        # Find the objects who do not have a parent or whose parent we are
        # not exporting
        self.RootExportList = [Object for Object in ExportMap.values()
            if Object.BlenderObject.parent not in ExportList]
        self.RootExportList = Util.SortByNameField(self.RootExportList)
        
        self.ExportList = Util.SortByNameField(ExportMap.values())

        # Determine each object's children from the pool of ExportObjects
        for Object in ExportMap.values():
            Children = Object.BlenderObject.children
            Object.Children = []
            for Child in Children:
                if Child in ExportMap:
                    Object.Children.append(ExportMap[Child])
        self.Log("Done")
        
        self.AnimationWriter = None
        if self.Config.ExportAnimation:
            self.Log("Gathering animation data...")
            
            # Collect all animated object data
            AnimationGenerators = self.__GatherAnimationGenerators()
            
            # Split the data up into animation sets based on user options
            if self.Config.ExportActionsAsSets:
                self.AnimationWriter = SplitSetAnimationWriter(self.Config,
                    self, AnimationGenerators)
            else:
                self.AnimationWriter = JoinedSetAnimationWriter(self.Config,
                    self, AnimationGenerators)
            self.Log("Done")

    # "Public" Interface

    def Export(self):
        self.Log("Exporting to {}".format(self.File.FilePath),
            MessageVerbose=False)

        # Export everything
        self.Log("Opening file...")
        self.File.Open()
        self.Log("Done")

        self.Log("Writing header...")
        self.__WriteHeader()
        self.Log("Done")

        self.Log("Opening Root frame...")
        self.__OpenRootFrame()
        self.Log("Done")

        self.Log("Writing objects...")
        for Object in self.RootExportList:
            Object.Write()
        self.Log("Done writing objects")

        self.Log("Closing Root frame...")
        self.__CloseRootFrame()
        self.Log("Done")
        
        if self.AnimationWriter is not None:
            self.Log("Writing animation set(s)...")
            self.AnimationWriter.WriteAnimationSets()
            self.Log("Done writing animation set(s)")

        self.Log("Closing file...")
        self.File.Close()
        self.Log("Done")

    def Log(self, String, MessageVerbose=True):
        if self.Config.Verbose is True or MessageVerbose == False:
            print(String)

    # "Private" Methods

    def __WriteHeader(self):
        self.File.Write("xof 0303txt 0032\n\n")

        # Write the headers that are required by some engines as needed

        if self.Config.IncludeFrameRate:
            self.File.Write("template AnimTicksPerSecond {\n\
  <9E415A43-7BA6-4a73-8743-B73D47E88476>\n\
  DWORD AnimTicksPerSecond;\n\
}\n\n")
        if self.Config.ExportSkinWeights:
            self.File.Write("template XSkinMeshHeader {\n\
  <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\
  WORD nMaxSkinWeightsPerVertex;\n\
  WORD nMaxSkinWeightsPerFace;\n\
  WORD nBones;\n\
}\n\n\
template SkinWeights {\n\
  <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
  STRING transformNodeName;\n\
  DWORD nWeights;\n\
  array DWORD vertexIndices[nWeights];\n\
  array float weights[nWeights];\n\
  Matrix4x4 matrixOffset;\n\
}\n\n")

    # Start the Root frame and write its transform matrix
    def __OpenRootFrame(self):
        self.File.Write("Frame Root {\n")
        self.File.Indent()

        self.File.Write("FrameTransformMatrix {\n")
        self.File.Indent()
        
        # Write the matrix that will convert Blender's coordinate space into
        # DirectX's.
        Util.WriteMatrix(self.File, self.SystemMatrix)
        
        self.File.Unindent()
        self.File.Write("}\n")

    def __CloseRootFrame(self):
        self.File.Unindent()
        self.File.Write("} // End of Root\n")
    
    def __GatherAnimationGenerators(self):
        Generators = []
        
        # If all animation data is to be lumped into one AnimationSet,
        if not self.Config.ExportActionsAsSets:
            # Build the appropriate generators for each object's type
            for Object in self.ExportList:
                if Object.BlenderObject.type == 'ARMATURE':
                    Generators.append(ArmatureAnimationGenerator(self.Config, 
                        None, Object))
                else:
                    Generators.append(GenericAnimationGenerator(self.Config,
                        None, Object))
        # Otherwise,
        else:
            # Keep track of which objects have no action.  These will be
            # lumped together in a Default_Action AnimationSet.
            ActionlessObjects = []
            
            for Object in self.ExportList:
                if Object.BlenderObject.animation_data is None:
                    ActionlessObjects.append(Object)
                    continue
                else:
                    if Object.BlenderObject.animation_data.action is None:
                        ActionlessObjects.append(Object)
                        continue
                
                # If an object has an action, build its appropriate generator
                if Object.BlenderObject.type == 'ARMATURE':
                    Generators.append(ArmatureAnimationGenerator(self.Config,
                        Util.SafeName(
                            Object.BlenderObject.animation_data.action.name),
                        Object))
                else:
                    Generators.append(GenericAnimationGenerator(self.Config,
                        Util.SafeName(
                            Object.BlenderObject.animation_data.action.name),
                        Object))
            
            # If we should export unused actions as if the first armature was
            # using them,
            if self.Config.AttachToFirstArmature:
                # Find the first armature
                FirstArmature = None
                for Object in self.ExportList:
                    if Object.BlenderObject.type == 'ARMATURE':
                        FirstArmature = Object
                        break
                    
                if FirstArmature is not None:
                    # Determine which actions are not used
                    UsedActions = [BlenderObject.animation_data.action
                        for BlenderObject in bpy.data.objects
                        if BlenderObject.animation_data is not None]
                    FreeActions = [Action for Action in bpy.data.actions
                        if Action not in UsedActions]
                    
                    # If the first armature has no action, remove it from the
                    # actionless objects so it doesn't end up in Default_Action
                    if FirstArmature in ActionlessObjects and len(FreeActions):
                        ActionlessObjects.remove(FirstArmature)
                    
                    # Keep track of the first armature's animation data so we
                    # can restore it after export
                    OldAction = None
                    NoData = False
                    if FirstArmature.BlenderObject.animation_data is not None:
                        OldAction = \
                            FirstArmature.BlenderObject.animation_data.action
                    else:
                        NoData = True
                        FirstArmature.BlenderObject.animation_data_create()
                    
                    # Build a generator for each unused action
                    for Action in FreeActions:
                        FirstArmature.BlenderObject.animation_data.action = \
                            Action
                        
                        Generators.append(ArmatureAnimationGenerator(
                            self.Config, Util.SafeName(Action.name),
                            FirstArmature))
                    
                    # Restore old animation data
                    FirstArmature.BlenderObject.animation_data.action = \
                        OldAction
                        
                    if NoData:
                        FirstArmature.BlenderObject.animation_data_clear()
            
            # Build a special generator for all actionless objects
            if len(ActionlessObjects):
                Generators.append(GroupAnimationGenerator(self.Config,
                    "Default_Action", ActionlessObjects))

        return Generators        

# This class wraps a Blender object and writes its data to the file
class ExportObject: # Base class, do not use
    def __init__(self, Config, Exporter, BlenderObject):
        self.Config = Config
        self.Exporter = Exporter
        self.BlenderObject = BlenderObject

        self.name = self.BlenderObject.name # Simple alias
        self.SafeName = Util.SafeName(self.BlenderObject.name)
        self.Children = []

    def __repr__(self):
        return "[ExportObject: {}]".format(self.BlenderObject.name)

    # "Public" Interface

    def Write(self):
        self.Exporter.Log("Opening frame for {}".format(self))
        self._OpenFrame()

        self.Exporter.Log("Writing children of {}".format(self))
        self._WriteChildren()

        self._CloseFrame()
        self.Exporter.Log("Closed frame of {}".format(self))

    # "Protected" Interface

    def _OpenFrame(self):
        self.Exporter.File.Write("Frame {} {{\n".format(self.SafeName))
        self.Exporter.File.Indent()

        self.Exporter.File.Write("FrameTransformMatrix {\n")
        self.Exporter.File.Indent()
        Util.WriteMatrix(self.Exporter.File, self.BlenderObject.matrix_local)
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}\n")

    def _CloseFrame(self):
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {}\n".format(self.SafeName))

    def _WriteChildren(self):
        for Child in Util.SortByNameField(self.Children):
            Child.Write()

# Simple decorator implemenation for ExportObject.  Used by empty objects
class EmptyExportObject(ExportObject):
    def __init__(self, Config, Exporter, BlenderObject):
        ExportObject.__init__(self, Config, Exporter, BlenderObject)

    def __repr__(self):
        return "[EmptyExportObject: {}]".format(self.name)
    
# Mesh object implementation of ExportObject
class MeshExportObject(ExportObject):
    def __init__(self, Config, Exporter, BlenderObject):
        ExportObject.__init__(self, Config, Exporter, BlenderObject)

    def __repr__(self):
        return "[MeshExportObject: {}]".format(self.name)

    # "Public" Interface

    def Write(self):
        self.Exporter.Log("Opening frame for {}".format(self))
        self._OpenFrame()

        if self.Config.ExportMeshes:
            self.Exporter.Log("Generating mesh for export...")
            # Generate the export mesh
            Mesh = None
            if self.Config.ApplyModifiers:
                # Certain modifiers shouldn't be applied in some cases
                # Deactivate them until after mesh generation is complete
                
                DeactivatedModifierList = []
                
                # If we're exporting armature data, we shouldn't apply
                # armature modifiers to the mesh
                if self.Config.ExportSkinWeights:
                    DeactivatedModifierList = [Modifier
                        for Modifier in self.BlenderObject.modifiers
                        if Modifier.type == 'ARMATURE' and \
                        Modifier.show_viewport]
                
                for Modifier in DeactivatedModifierList:
                    Modifier.show_viewport = False
                        
                Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
                    True, 'PREVIEW')
                
                # Restore the deactivated modifiers
                for Modifier in DeactivatedModifierList:
                    Modifier.show_viewport = True   
            else:
                Mesh = self.BlenderObject.to_mesh(self.Exporter.context.scene,
                    False, 'PREVIEW')
            self.Exporter.Log("Done")
                    
            self.__WriteMesh(Mesh)

            # Cleanup
            bpy.data.meshes.remove(Mesh)

        self.Exporter.Log("Writing children of {}".format(self))
        self._WriteChildren()

        self._CloseFrame()
        self.Exporter.Log("Closed frame of {}".format(self))

    # "Protected"
    
    # This class provides a general system for indexing a mesh, depending on
    # exporter needs.  For instance, some options require us to duplicate each
    # vertex of each face, some can reuse vertex data.  For those we'd use
    # _UnrolledFacesMeshEnumerator and _OneToOneMeshEnumerator respectively.
    class _MeshEnumerator:
        def __init__(self, Mesh):
            self.Mesh = Mesh
            
            # self.vertices and self.PolygonVertexIndexes relate to the
            # original mesh in the following way:
            
            # Mesh.vertices[Mesh.polygons[x].vertices[y]] == 
            # self.vertices[self.PolygonVertexIndexes[x][y]]
            
            self.vertices = None 
            self.PolygonVertexIndexes = None
    
    # Represents the mesh as it is inside Blender
    class _OneToOneMeshEnumerator(_MeshEnumerator):
        def __init__(self, Mesh):
            MeshExportObject._MeshEnumerator.__init__(self, Mesh)
            
            self.vertices = Mesh.vertices
            
            self.PolygonVertexIndexes = tuple(tuple(Polygon.vertices)
                for Polygon in Mesh.polygons)

    # Duplicates each vertex for each face
    class _UnrolledFacesMeshEnumerator(_MeshEnumerator):
        def __init__(self, Mesh):
            MeshExportObject._MeshEnumerator.__init__(self, Mesh)
            
            self.vertices = tuple()
            for Polygon in Mesh.polygons:
                self.vertices += tuple(Mesh.vertices[VertexIndex]
                    for VertexIndex in Polygon.vertices)
            
            self.PolygonVertexIndexes = []
            Index = 0
            for Polygon in Mesh.polygons:
                self.PolygonVertexIndexes.append(tuple(range(Index, 
                    Index + len(Polygon.vertices))))
                Index += len(Polygon.vertices)
            
    # "Private" Methods

    def __WriteMesh(self, Mesh):
        self.Exporter.Log("Writing mesh vertices...")
        self.Exporter.File.Write("Mesh {{ // {} mesh\n".format(self.SafeName))
        self.Exporter.File.Indent()
        
        # Create the mesh enumerator based on options
        MeshEnumerator = None
        if (self.Config.ExportUVCoordinates and Mesh.uv_textures) or \
            (self.Config.ExportVertexColors and Mesh.vertex_colors) or \
            (self.Config.ExportSkinWeights):
            MeshEnumerator = MeshExportObject._UnrolledFacesMeshEnumerator(Mesh)
        else:
            MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
        
        # Write vertex positions
        VertexCount = len(MeshEnumerator.vertices)
        self.Exporter.File.Write("{};\n".format(VertexCount))
        for Index, Vertex in enumerate(MeshEnumerator.vertices):
            Position = Vertex.co
            self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(
                        Position[0], Position[1], Position[2]))
            
            if Index == VertexCount - 1:
                self.Exporter.File.Write(";\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)
        
        # Write face definitions
        PolygonCount = len(MeshEnumerator.PolygonVertexIndexes)
        self.Exporter.File.Write("{};\n".format(PolygonCount))
        for Index, PolygonVertexIndexes in \
            enumerate(MeshEnumerator.PolygonVertexIndexes):
            
            self.Exporter.File.Write("{};".format(len(PolygonVertexIndexes)))
            
            PolygonVertexIndexes = PolygonVertexIndexes[::-1]
            
            for VertexIndex in PolygonVertexIndexes:
                self.Exporter.File.Write("{},".format(VertexIndex),
                    Indent=False)
            
            if Index == PolygonCount - 1:
                self.Exporter.File.Write(";\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)
        self.Exporter.Log("Done")
        
        # Write the other mesh components
            
        if self.Config.ExportNormals:
            self.Exporter.Log("Writing mesh normals...")
            self.__WriteMeshNormals(Mesh)
            self.Exporter.Log("Done")
            
        if self.Config.ExportUVCoordinates:
            self.Exporter.Log("Writing mesh UV coordinates...")
            self.__WriteMeshUVCoordinates(Mesh)
            self.Exporter.Log("Done")

        if self.Config.ExportMaterials:
            self.Exporter.Log("Writing mesh materials...")
            self.__WriteMeshMaterials(Mesh)
            self.Exporter.Log("Done")
        
        if self.Config.ExportVertexColors:
            self.Exporter.Log("Writing mesh vertex colors...")
            self.__WriteMeshVertexColors(Mesh, MeshEnumerator=MeshEnumerator)
            self.Exporter.Log("Done")
        
        if self.Config.ExportSkinWeights:
            self.Exporter.Log("Writing mesh skin weights...")
            self.__WriteMeshSkinWeights(Mesh, MeshEnumerator=MeshEnumerator)
            self.Exporter.Log("Done")

        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {} mesh\n".format(self.SafeName))

    def __WriteMeshNormals(self, Mesh, MeshEnumerator=None):
        # Since mesh normals only need their face counts and vertices per face
        # to match up with the other mesh data, we can optimize export with
        # this enumerator.  Exports each vertex's normal when a face is shaded
        # smooth, and exports the face normal only once when a face is shaded
        # flat.
        class _NormalsMeshEnumerator(MeshExportObject._MeshEnumerator):
            def __init__(self, Mesh):
                MeshExportObject._MeshEnumerator(Mesh)
                
                self.vertices = []
                self.PolygonVertexIndexes = []
                
                Index = 0
                for Polygon in Mesh.polygons:
                    if not Polygon.use_smooth:
                        self.vertices.append(Polygon)
                        self.PolygonVertexIndexes.append(
                            tuple(len(Polygon.vertices) * [Index]))
                        Index += 1
                    else:
                        for VertexIndex in Polygon.vertices:
                            self.vertices.append(Mesh.vertices[VertexIndex])
                        self.PolygonVertexIndexes.append(
                            tuple(range(Index, Index + len(Polygon.vertices))))
                        Index += len(Polygon.vertices)            
        
        if MeshEnumerator is None:
            MeshEnumerator = _NormalsMeshEnumerator(Mesh)
        
        self.Exporter.File.Write("MeshNormals {{ // {} normals\n".format(
            self.SafeName))
        self.Exporter.File.Indent()
        
        NormalCount = len(MeshEnumerator.vertices)
        self.Exporter.File.Write("{};\n".format(NormalCount))
        
        # Write mesh normals.
        for Index, Vertex in enumerate(MeshEnumerator.vertices):
            Normal = Vertex.normal
            if self.Config.FlipNormals:
                Normal = -1.0 * Vertex.normal
            
            self.Exporter.File.Write("{:9f};{:9f};{:9f};".format(Normal[0],
                Normal[1], Normal[2]))
            
            if Index == NormalCount - 1:
                self.Exporter.File.Write(";\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)
        
        # Write face definitions.
        FaceCount = len(MeshEnumerator.PolygonVertexIndexes)
        self.Exporter.File.Write("{};\n".format(FaceCount))
        for Index, Polygon in enumerate(MeshEnumerator.PolygonVertexIndexes):
            self.Exporter.File.Write("{};".format(len(Polygon)))
            
            # Reverse the winding order
            for VertexIndex in Polygon[::-1]:
                self.Exporter.File.Write("{},".format(VertexIndex),
                    Indent=False)
            
            if Index == FaceCount - 1:
                self.Exporter.File.Write(";\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)

        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {} normals\n".format(
            self.SafeName))
     
    def __WriteMeshUVCoordinates(self, Mesh):
        if not Mesh.uv_textures:
            return
        
        self.Exporter.File.Write("MeshTextureCoords {{ // {} UV coordinates\n" \
            .format(self.SafeName))
        self.Exporter.File.Indent()
        
        UVCoordinates = Mesh.uv_layers.active.data
        
        VertexCount = 0
        for Polygon in Mesh.polygons:
            VertexCount += len(Polygon.vertices)
        
        # Gather and write UV coordinates
        Index = 0
        self.Exporter.File.Write("{};\n".format(VertexCount))
        for Polygon in Mesh.polygons:
            Vertices = []
            for Vertex in [UVCoordinates[Vertex] for Vertex in
                Polygon.loop_indices]:
                Vertices.append(tuple(Vertex.uv))
            for Vertex in Vertices:
                self.Exporter.File.Write("{:9f};{:9f};".format(Vertex[0],
                    1.0 - Vertex[1]))
                Index += 1
                if Index == VertexCount:
                    self.Exporter.File.Write(";\n", Indent=False)
                else:
                    self.Exporter.File.Write(",\n", Indent=False)
                    
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {} UV coordinates\n".format(
            self.SafeName))

    def __WriteMeshMaterials(self, Mesh):
        def WriteMaterial(Exporter, Material):
            def GetMaterialTextureFileName(Material):
                if Material:
                    # Create a list of Textures that have type 'IMAGE'
                    ImageTextures = [Material.texture_slots[TextureSlot].texture
                        for TextureSlot in Material.texture_slots.keys()
                        if Material.texture_slots[TextureSlot].texture.type ==
                        'IMAGE']
                    # Refine to only image file names if applicable
                    ImageFiles = [bpy.path.basename(Texture.image.filepath)
                        for Texture in ImageTextures
                        if getattr(Texture.image, "source", "") == 'FILE']
                    if ImageFiles:
                        return ImageFiles[0]
                return None
            
            Exporter.File.Write("Material {} {{\n".format(
                Util.SafeName(Material.name)))
            Exporter.File.Indent()
            
            Diffuse = list(Vector(Material.diffuse_color) *
                Material.diffuse_intensity)
            Diffuse.append(Material.alpha)
            # Map Blender's range of 1 - 511 to 0 - 1000
            Specularity = 1000 * (Material.specular_hardness - 1.0) / 510.0
            Specular = list(Vector(Material.specular_color) *
                Material.specular_intensity)
            
            Exporter.File.Write("{:9f};{:9f};{:9f};{:9f};;\n".format(Diffuse[0],
                Diffuse[1], Diffuse[2], Diffuse[3]))
            Exporter.File.Write(" {:9f};\n".format(Specularity))
            Exporter.File.Write("{:9f};{:9f};{:9f};;\n".format(Specular[0],
                Specular[1], Specular[2]))
            Exporter.File.Write(" 0.000000; 0.000000; 0.000000;;\n")
            
            TextureFileName = GetMaterialTextureFileName(Material)
            if TextureFileName:
                Exporter.File.Write("TextureFilename {{\"{}\";}}\n".format(
                    TextureFileName))
            
            Exporter.File.Unindent()
            Exporter.File.Write("}\n");
        
        Materials = Mesh.materials
        # Do not write materials if there are none
        if not Materials.keys():
            return
        
        self.Exporter.File.Write("MeshMaterialList {{ // {} material list\n".
            format(self.SafeName))
        self.Exporter.File.Indent()
        
        self.Exporter.File.Write("{};\n".format(len(Materials)))
        self.Exporter.File.Write("{};\n".format(len(Mesh.polygons)))
        # Write a material index for each face
        for Index, Polygon in enumerate(Mesh.polygons):
            self.Exporter.File.Write("{}".format(Polygon.material_index))
            if Index == len(Mesh.polygons) - 1:
                self.Exporter.File.Write(";;\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)
        
        for Material in Materials:
            WriteMaterial(self.Exporter, Material)
        
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {} material list\n".format(
            self.SafeName))
    
    def __WriteMeshVertexColors(self, Mesh, MeshEnumerator=None):
        # If there are no vertex colors, don't write anything
        if len(Mesh.vertex_colors) == 0:
            return
        
        # Blender stores vertex color information per vertex per face, so we
        # need to pass in an _UnrolledFacesMeshEnumerator.  Otherwise,
        if MeshEnumerator is None:
            MeshEnumerator = _UnrolledFacesMeshEnumerator(Mesh)
        
        # Gather the colors of each vertex
        VertexColorLayer = Mesh.vertex_colors.active
        VertexColors = [VertexColorLayer.data[Index].color for Index in
            range(0,len(MeshEnumerator.vertices))]
        VertexColorCount = len(VertexColors)
        
        self.Exporter.File.Write("MeshVertexColors {{ // {} vertex colors\n" \
            .format(self.SafeName))
        self.Exporter.File.Indent()
        self.Exporter.File.Write("{};\n".format(VertexColorCount))
        
        # Write the vertex colors for each vertex index.
        for Index, Color in enumerate(VertexColors):
            self.Exporter.File.Write("{};{:9f};{:9f};{:9f};{:9f};;".format(
                Index, Color[0], Color[1], Color[2], 1.0))
            
            if Index == VertexColorCount - 1:
                self.Exporter.File.Write(";\n", Indent=False)
            else:
                self.Exporter.File.Write(",\n", Indent=False)
        
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {} vertex colors\n".format(
            self.SafeName))
    
    def __WriteMeshSkinWeights(self, Mesh, MeshEnumerator=None):
        # This contains vertex indexes and weights for the vertices that belong
        # to this bone's group.  Also calculates the bone skin matrix.
        class _BoneVertexGroup:
                def __init__(self, BlenderObject, ArmatureObject, BoneName):
                    self.BoneName = BoneName
                    self.SafeName = Util.SafeName(ArmatureObject.name) + "_" + \
                        Util.SafeName(BoneName)
                    
                    self.Indexes = []
                    self.Weights = []
                    
                    # BoneMatrix transforms mesh vertices into the
                    # space of the bone.
                    # Here are the final transformations in order:
                    #  - Object Space to World Space
                    #  - World Space to Armature Space
                    #  - Armature Space to Bone Space
                    # This way, when BoneMatrix is transformed by the bone's
                    # Frame matrix, the vertices will be in their final world
                    # position.
                    
                    self.BoneMatrix = ArmatureObject.data.bones[BoneName] \
                        .matrix_local.inverted()
                    self.BoneMatrix *= ArmatureObject.matrix_world.inverted()
                    self.BoneMatrix *= BlenderObject.matrix_world
                
                def AddVertex(self, Index, Weight):
                    self.Indexes.append(Index)
                    self.Weights.append(Weight)
        
        # Skin weights work well with vertex reuse per face.  Use a
        # _OneToOneMeshEnumerator if possible.
        if MeshEnumerator is None:
            MeshEnumerator = MeshExportObject._OneToOneMeshEnumerator(Mesh)
        
        ArmatureModifierList = [Modifier 
            for Modifier in self.BlenderObject.modifiers
            if Modifier.type == 'ARMATURE' and Modifier.show_viewport]
        
        if not ArmatureModifierList:
            return
        
        # Although multiple armature objects are gathered, support for
        # multiple armatures per mesh is not complete
        ArmatureObjects = [Modifier.object for Modifier in ArmatureModifierList]
        
        for ArmatureObject in ArmatureObjects:
            # Determine the names of the bone vertex groups
            PoseBoneNames = [Bone.name for Bone in ArmatureObject.pose.bones]
            VertexGroupNames = [Group.name for Group
                in self.BlenderObject.vertex_groups]
            UsedBoneNames = set(PoseBoneNames).intersection(VertexGroupNames)
            
            # Create a _BoneVertexGroup for each group name
            BoneVertexGroups = [_BoneVertexGroup(self.BlenderObject,
                ArmatureObject, BoneName) for BoneName in UsedBoneNames]
            
            # Maps Blender's internal group indexing to our _BoneVertexGroups
            GroupIndexToBoneVertexGroups = {Group.index : BoneVertexGroup
                for Group in self.BlenderObject.vertex_groups
                for BoneVertexGroup in BoneVertexGroups
                if Group.name == BoneVertexGroup.BoneName}
            
            MaximumInfluences = 0
            
            for Index, Vertex in enumerate(MeshEnumerator.vertices):
                VertexWeightTotal = 0.0
                VertexInfluences = 0
                
                # Sum up the weights of groups that correspond
                # to armature bones.
                for VertexGroup in Vertex.groups:
                    BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
                        VertexGroup.group)
                    if BoneVertexGroup is not None:
                        VertexWeightTotal += VertexGroup.weight
                        VertexInfluences += 1
                
                if VertexInfluences > MaximumInfluences:
                    MaximumInfluences = VertexInfluences
                
                # Add the vertex to the bone vertex groups it belongs to,
                # normalizing each bone's weight.
                for VertexGroup in Vertex.groups:
                    BoneVertexGroup = GroupIndexToBoneVertexGroups.get(
                        VertexGroup.group)
                    if BoneVertexGroup is not None:
                        Weight = VertexGroup.weight / VertexWeightTotal
                        BoneVertexGroup.AddVertex(Index, Weight)
            
            self.Exporter.File.Write("XSkinMeshHeader {\n")
            self.Exporter.File.Indent()
            self.Exporter.File.Write("{};\n".format(MaximumInfluences))
            self.Exporter.File.Write("{};\n".format(3 * MaximumInfluences))
            self.Exporter.File.Write("{};\n".format(len(BoneVertexGroups)))
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}\n")
            
            for BoneVertexGroup in BoneVertexGroups:
                self.Exporter.File.Write("SkinWeights {\n")
                self.Exporter.File.Indent()
                self.Exporter.File.Write("\"{}\";\n".format(
                    BoneVertexGroup.SafeName))
                
                GroupVertexCount = len(BoneVertexGroup.Indexes)
                self.Exporter.File.Write("{};\n".format(GroupVertexCount))
                
                # Write the indexes of the vertices this bone affects.
                for Index, VertexIndex in enumerate(BoneVertexGroup.Indexes):
                    self.Exporter.File.Write("{}".format(VertexIndex))
                    
                    if Index == GroupVertexCount - 1:
                        self.Exporter.File.Write(";\n", Indent=False)
                    else:
                        self.Exporter.File.Write(",\n", Indent=False)
                
                # Write the weights of the affected vertices.
                for Index, VertexWeight in enumerate(BoneVertexGroup.Weights):
                    self.Exporter.File.Write("{:9f}".format(VertexWeight))
                    
                    if Index == GroupVertexCount - 1:
                        self.Exporter.File.Write(";\n", Indent=False)
                    else:
                        self.Exporter.File.Write(",\n", Indent=False)
                
                # Write the bone's matrix.
                Util.WriteMatrix(self.Exporter.File, BoneVertexGroup.BoneMatrix)
            
                self.Exporter.File.Unindent()
                self.Exporter.File.Write("}} // End of {} skin weights\n" \
                    .format(BoneVertexGroup.SafeName))
            
# Armature object implementation of ExportObject            
class ArmatureExportObject(ExportObject):
    def __init__(self, Config, Exporter, BlenderObject):
        ExportObject.__init__(self, Config, Exporter, BlenderObject)

    def __repr__(self):
        return "[ArmatureExportObject: {}]".format(self.name)
    
    # "Public" Interface

    def Write(self):
        self.Exporter.Log("Opening frame for {}".format(self))
        self._OpenFrame()
        
        if self.Config.ExportArmatureBones:
            Armature = self.BlenderObject.data
            RootBones = [Bone for Bone in Armature.bones if Bone.parent is None]
            self.Exporter.Log("Writing frames for armature bones...")
            self.__WriteBones(RootBones)
            self.Exporter.Log("Done")

        self.Exporter.Log("Writing children of {}".format(self))
        self._WriteChildren()

        self._CloseFrame()
        self.Exporter.Log("Closed frame of {}".format(self))
    
    # "Private" Methods
    
    def __WriteBones(self, Bones):
        # Simply export the frames for each bone.  Export in rest position or
        # posed position depending on options.
        for Bone in Bones:
            BoneMatrix = Matrix()
            
            if self.Config.ExportRestBone:
                if Bone.parent:
                    BoneMatrix = Bone.parent.matrix_local.inverted()
                BoneMatrix *= Bone.matrix_local
            else:
                PoseBone = self.BlenderObject.pose.bones[Bone.name]
                if Bone.parent:
                    BoneMatrix = PoseBone.parent.matrix.inverted()
                BoneMatrix *= PoseBone.matrix
            
            BoneSafeName = self.SafeName + "_" + \
                Util.SafeName(Bone.name)
            self.__OpenBoneFrame(BoneSafeName, BoneMatrix)
            
            self.__WriteBoneChildren(Bone)
            
            self.__CloseBoneFrame(BoneSafeName)
            
    
    def __OpenBoneFrame(self, BoneSafeName, BoneMatrix):
        self.Exporter.File.Write("Frame {} {{\n".format(BoneSafeName))
        self.Exporter.File.Indent()

        self.Exporter.File.Write("FrameTransformMatrix {\n")
        self.Exporter.File.Indent()
        Util.WriteMatrix(self.Exporter.File, BoneMatrix)
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}\n")
    
    def __CloseBoneFrame(self, BoneSafeName):
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}} // End of {}\n".format(BoneSafeName))
    
    def __WriteBoneChildren(self, Bone):
        self.__WriteBones(Util.SortByNameField(Bone.children))


# Container for animation data
class Animation:
    def __init__(self, SafeName):
        self.SafeName = SafeName
        
        self.RotationKeys = []
        self.ScaleKeys = []
        self.PositionKeys = []
        
    # "Public" Interface
    
    def GetKeyCount(self):
        return len(self.RotationKeys)


# Creates a list of Animation objects based on the animation needs of the
# ExportObject passed to it
class AnimationGenerator: # Base class, do not use
    def __init__(self, Config, SafeName, ExportObject):
        self.Config = Config
        self.SafeName = SafeName
        self.ExportObject = ExportObject
        
        self.Animations = []


# Creates one Animation object that contains the rotation, scale, and position
# of the ExportObject
class GenericAnimationGenerator(AnimationGenerator):
    def __init__(self, Config, SafeName, ExportObject):
        AnimationGenerator.__init__(self, Config, SafeName, ExportObject)
        
        self._GenerateKeys()
    
    # "Protected" Interface
    
    def _GenerateKeys(self):
        Scene = bpy.context.scene # Convenience alias
        BlenderCurrentFrame = Scene.frame_current
        
        CurrentAnimation = Animation(self.ExportObject.SafeName)
        BlenderObject = self.ExportObject.BlenderObject
        
        for Frame in range(Scene.frame_start, Scene.frame_end + 1):
            Scene.frame_set(Frame)
            
            Rotation = BlenderObject.rotation_euler.to_quaternion()
            Scale = BlenderObject.matrix_local.to_scale()
            Position = BlenderObject.matrix_local.to_translation()
            
            CurrentAnimation.RotationKeys.append(Rotation)
            CurrentAnimation.ScaleKeys.append(Scale)
            CurrentAnimation.PositionKeys.append(Position)
        
        self.Animations.append(CurrentAnimation)
        Scene.frame_set(BlenderCurrentFrame)

        
# Creates one Animation object for each of the ExportObjects it gets passed.
# Essentially a bunch of GenericAnimationGenerators lumped into one.
class GroupAnimationGenerator(AnimationGenerator):
    def __init__(self, Config, SafeName, ExportObjects):
        AnimationGenerator.__init__(self, Config, SafeName, None)
        self.ExportObjects = ExportObjects
        
        self._GenerateKeys()
    
    # "Protected" Interface
    
    def _GenerateKeys(self):
        for Object in self.ExportObjects:
            if Object.BlenderObject.type == 'ARMATURE':
                TemporaryGenerator = ArmatureAnimationGenerator(self.Config,
                    None, Object)
                self.Animations += TemporaryGenerator.Animations
            else:
                TemporaryGenerator = GenericAnimationGenerator(self.Config,
                    None, Object)
                self.Animations += TemporaryGenerator.Animations


# Creates an Animation object for the ArmatureExportObject it gets passed and
# an Animation object for each bone in the armature (if options allow)
class ArmatureAnimationGenerator(GenericAnimationGenerator):
    def __init__(self, Config, SafeName, ArmatureExportObject):
        GenericAnimationGenerator.__init__(self, Config, SafeName,
            ArmatureExportObject)
        
        if self.Config.ExportArmatureBones:
            self._GenerateBoneKeys()
        
    # "Protected" Interface
    
    def _GenerateBoneKeys(self):
        from itertools import zip_longest as zip
        
        Scene = bpy.context.scene # Convenience alias
        BlenderCurrentFrame = Scene.frame_current
        
        ArmatureObject = self.ExportObject.BlenderObject
        ArmatureSafeName = self.ExportObject.SafeName
        
        # Create Animation objects for each bone
        BoneAnimations = [Animation(ArmatureSafeName + "_" + \
            Util.SafeName(Bone.name)) for Bone in ArmatureObject.pose.bones]
        
        for Frame in range(Scene.frame_start, Scene.frame_end + 1):
            Scene.frame_set(Frame)
            
            for Bone, BoneAnimation in \
                zip(ArmatureObject.pose.bones, BoneAnimations):
                
                Rotation = ArmatureObject.data.bones[Bone.name] \
                    .matrix.to_quaternion() * \
                    Bone.rotation_quaternion
                
                PoseMatrix = Matrix()
                if Bone.parent:
                    PoseMatrix = Bone.parent.matrix.inverted()
                PoseMatrix *= Bone.matrix
                
                Scale = PoseMatrix.to_scale()
                Position = PoseMatrix.to_translation()
                
                BoneAnimation.RotationKeys.append(Rotation)
                BoneAnimation.ScaleKeys.append(Scale)
                BoneAnimation.PositionKeys.append(Position)
        
        self.Animations += BoneAnimations
        Scene.frame_set(BlenderCurrentFrame)


# Container for all AnimationGenerators that belong in a single AnimationSet
class AnimationSet:
    def __init__(self, SafeName, AnimationGenerators):
        self.SafeName = SafeName
        self.AnimationGenerators = AnimationGenerators


# Writes all animation data to file.  Implementations will control the
# separation of AnimationGenerators into distinct AnimationSets.
class AnimationWriter:
    def __init__(self, Config, Exporter, AnimationGenerators):
        self.Config = Config
        self.Exporter = Exporter
        self.AnimationGenerators = AnimationGenerators
        
        self.AnimationSets = []
        
    # "Public" Interface
    
    # Writes all AnimationSets.  Implementations probably won't have to override
    # this method.
    def WriteAnimationSets(self):
        if self.Config.IncludeFrameRate:
            self.Exporter.Log("Writing frame rate...")
            self.__WriteFrameRate()
            self.Exporter.Log("Done")
            
        for Set in self.AnimationSets:
            self.Exporter.Log("Writing animation set {}".format(Set.SafeName))
            self.Exporter.File.Write("AnimationSet {} {{\n".format(
                Set.SafeName))
            self.Exporter.File.Indent()
            
            # Write each animation of each generator
            for Generator in Set.AnimationGenerators:
                for CurrentAnimation in Generator.Animations:
                    self.Exporter.Log("Writing animation of {}".format(
                        CurrentAnimation.SafeName))
                    self.Exporter.File.Write("Animation {\n")
                    self.Exporter.File.Indent()
                    self.Exporter.File.Write("{{{}}}\n".format(
                        CurrentAnimation.SafeName))
                    
                    KeyCount = CurrentAnimation.GetKeyCount()
                    
                    # Write rotation keys
                    self.Exporter.File.Write("AnimationKey { // Rotation\n");
                    self.Exporter.File.Indent()
                    self.Exporter.File.Write("0;\n")
                    self.Exporter.File.Write("{};\n".format(KeyCount))
                    
                    for Frame, Key in enumerate(CurrentAnimation.RotationKeys):
                        self.Exporter.File.Write(
                            "{};4;{:9f},{:9f},{:9f},{:9f};;".format(
                            Frame, -Key[0], Key[1], Key[2], Key[3]))
                        
                        if Frame == KeyCount - 1:
                            self.Exporter.File.Write(";\n", Indent=False)
                        else:
                            self.Exporter.File.Write(",\n", Indent=False)
                    
                    self.Exporter.File.Unindent()
                    self.Exporter.File.Write("}\n")
                    
                    # Write scale keys
                    self.Exporter.File.Write("AnimationKey { // Scale\n");
                    self.Exporter.File.Indent()
                    self.Exporter.File.Write("1;\n")
                    self.Exporter.File.Write("{};\n".format(KeyCount))
                    
                    for Frame, Key in enumerate(CurrentAnimation.ScaleKeys):
                        self.Exporter.File.Write(
                            "{};3;{:9f},{:9f},{:9f};;".format(
                            Frame, Key[0], Key[1], Key[2]))
                        
                        if Frame == KeyCount - 1:
                            self.Exporter.File.Write(";\n", Indent=False)
                        else:
                            self.Exporter.File.Write(",\n", Indent=False)
                    
                    self.Exporter.File.Unindent()
                    self.Exporter.File.Write("}\n")
                    
                    # Write position keys
                    self.Exporter.File.Write("AnimationKey { // Position\n");
                    self.Exporter.File.Indent()
                    self.Exporter.File.Write("2;\n")
                    self.Exporter.File.Write("{};\n".format(KeyCount))
                    
                    for Frame, Key in enumerate(CurrentAnimation.PositionKeys):
                        self.Exporter.File.Write(
                            "{};3;{:9f},{:9f},{:9f};;".format(
                            Frame, Key[0], Key[1], Key[2]))
                        
                        if Frame == KeyCount - 1:
                            self.Exporter.File.Write(";\n", Indent=False)
                        else:
                            self.Exporter.File.Write(",\n", Indent=False)
                    
                    self.Exporter.File.Unindent()
                    self.Exporter.File.Write("}\n")
                    
                    self.Exporter.File.Unindent()
                    self.Exporter.File.Write("}\n")
                    self.Exporter.Log("Done")
                    
            self.Exporter.File.Unindent()
            self.Exporter.File.Write("}} // End of AnimationSet {}\n".format(
                Set.SafeName))
            self.Exporter.Log("Done writing animation set {}".format(
                Set.SafeName))
    
    # "Private" Methods
    
    def __WriteFrameRate(self):
        Scene = bpy.context.scene # Convenience alias
        
        # Calculate the integer frame rate
        FrameRate = int(Scene.render.fps / Scene.render.fps_base)
        
        self.Exporter.File.Write("AnimTicksPerSecond {\n");
        self.Exporter.File.Indent()
        self.Exporter.File.Write("{};\n".format(FrameRate))
        self.Exporter.File.Unindent()
        self.Exporter.File.Write("}\n")

# Implementation of AnimationWriter that sticks all generators into a
# single AnimationSet
class JoinedSetAnimationWriter(AnimationWriter):
    def __init__(self, Config, Exporter, AnimationGenerators):
        AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators)
        
        self.AnimationSets = [AnimationSet("Global", self.AnimationGenerators)]

# Implementation of AnimationWriter that puts each generator into its
# own AnimationSet
class SplitSetAnimationWriter(AnimationWriter):
    def __init__(self, Config, Exporter, AnimationGenerators):
        AnimationWriter.__init__(self, Config, Exporter, AnimationGenerators)
        
        self.AnimationSets = [AnimationSet(Generator.SafeName, [Generator])
            for Generator in AnimationGenerators]


# Interface to the file.  Supports automatic whitespace indenting.
class File:
    def __init__(self, FilePath):
        self.FilePath = FilePath
        self.File = None
        self.__Whitespace = 0

    def Open(self):
        if not self.File:
            self.File = open(self.FilePath, 'w')

    def Close(self):
        self.File.close()
        self.File = None

    def Write(self, String, Indent=True):
        if Indent:
            # Escape any formatting braces
            String = String.replace("{", "{{")
            String = String.replace("}", "}}")
            self.File.write(("{}" + String).format("  " * self.__Whitespace))
        else:
            self.File.write(String)

    def Indent(self, Levels=1):
        self.__Whitespace += Levels

    def Unindent(self, Levels=1):
        self.__Whitespace -= Levels
        if self.__Whitespace < 0:
            self.__Whitespace = 0


# Static utilities
class Util:
    @staticmethod
    def SafeName(Name):
        # Replaces each character in OldSet with NewChar
        def ReplaceSet(String, OldSet, NewChar):
            for OldChar in OldSet:
                String = String.replace(OldChar, NewChar)
            return String

        import string

        NewName = ReplaceSet(Name, string.punctuation + " ", "_")
        if NewName[0].isdigit() or NewName in ["ARRAY", "DWORD", "UCHAR",
            "FLOAT", "ULONGLONG", "BINARY_RESOURCE", "SDWORD", "UNICODE",
            "CHAR", "STRING", "WORD", "CSTRING", "SWORD", "DOUBLE", "TEMPLATE"]:
            NewName = "_" + NewName
        return NewName

    @staticmethod
    def WriteMatrix(File, Matrix):
        File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][0],
            Matrix[1][0], Matrix[2][0], Matrix[3][0]))
        File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][1],
            Matrix[1][1], Matrix[2][1], Matrix[3][1]))
        File.Write("{:9f},{:9f},{:9f},{:9f},\n".format(Matrix[0][2],
            Matrix[1][2], Matrix[2][2], Matrix[3][2]))
        File.Write("{:9f},{:9f},{:9f},{:9f};;\n".format(Matrix[0][3],
            Matrix[1][3], Matrix[2][3], Matrix[3][3]))
    
    # Used on lists of blender objects and lists of ExportObjects, both of
    # which have a name field
    @staticmethod
    def SortByNameField(List):
        def SortKey(x):
            return x.name
        
        return sorted(List, key=SortKey)
