# ***** 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 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 General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
# ***** END GPL LICENCE BLOCK *****

#TODO:

# Set the command name so that DarkRadiant recognises this file
__commandName__ = 'aseExport'
__commandDisplayName__ = 'Export ASE...'

# The actual algorithm called by DarkRadiant is contained in the execute() function
def execute():
    script = "Dark Radiant ASCII Scene Export (*.ase)"
    author = "Richard Bartlett, some additions by greebo, tels and Obsttorte"
    version = "0.7"

    # Check if we have a valid selection

    selectionInfo = GlobalSelectionSystem.getSelectionInfo()

    # Don't allow empty selections or selected components only
    if selectionInfo.totalCount == 0 or selectionInfo.totalCount == selectionInfo.componentCount:
        errMsg = GlobalDialogManager.createMessageBox('No selection', 'Nothing selected, cannot run exporter.', Dialog.ERROR)
        errMsg.run()
        return

    shaderlist = []
    geomlist = []

    # simple linear triangulation of n-sided poly

    def triangulate(pointset):
        tris = []
        for count in range(1, len(pointset) - 1):
            tris.append([pointset[0], pointset[count], pointset[count + 1]])
        return tris

    # skin patch matrix with tris

    def skinmatrix(pointset, width, height):
        tris = []
        for h in range(height-1):
            for w in range(width-1):
                tris.append([pointset[w+(h*width)], pointset[w+1+(h*width)], pointset[w+width+(h*width)]])
                tris.append([pointset[w+1+(h*width)], pointset[w+1+width+(h*width)], pointset[w+width+(h*width)]])
        return tris

    def processBrush(brushnode):
        verts = []
        faces = []

        numfaces = brushnode.getNumFaces()
        for index in range(numfaces):
            facenode = brushnode.getFace(index)
            shader = facenode.getShader()

            # Tels: skip if caulk and no caulk should be exported
            if (shader == 'textures/common/caulk') and (int(GlobalRegistry.get('user/scripts/aseExport/exportcaulk'))) == 0:
                continue

            if not shader in shaderlist:
                shaderlist.append(shader)
            winding = facenode.getWinding()
            tris = triangulate([x+len(verts) for x in range(len(winding))])
            for x in tris:
                x.append(shaderlist.index(shader))
                faces.append(x)
            for x in reversed(winding):
                verts.append([x.vertex.x(), x.vertex.y(), x.vertex.z(), x.texcoord.x(), x.texcoord.y() * -1, x.normal.x(), x.normal.y(), x.normal.z()])

        geomlist.append([verts, faces])
        return

    def checkNAN(x):
	if x[5]!=x[5]:
	    x[5]=0
	if x[6]!=x[6]:
	    x[6]=0
	if x[7]!=x[7]:
	    x[7]=0
	return x

    def processPatch(patchnode):
        verts = []
        faces = []

        shader = patchnode.getShader()

        # Tels: skip if caulk and no caulk should be exported
        if shader == 'textures/common/caulk' and int(GlobalRegistry.get('user/scripts/aseExport/exportcaulk')) == 0:
            return

        if not shader in shaderlist:
            shaderlist.append(shader)
        mesh = patchnode.getTesselatedPatchMesh()
        for x in mesh.vertices:
            verts.append(checkNAN([x.vertex.x(), x.vertex.y(), x.vertex.z(), x.texcoord.x(), x.texcoord.y() * -1, x.normal.x(), x.normal.y(), x.normal.z()]))
        tris = skinmatrix([x for x in range(len(verts))], mesh.width, mesh.height)
        for x in tris:
            x.append(shaderlist.index(shader))
            faces.append(x)

        geomlist.append([verts, faces])
        return

    # Branch on primitive nodes
    def processPrimitive(scenenode):
        if scenenode.isBrush():
            processBrush(scenenode.getBrush())
        elif scenenode.isPatch():
            processPatch(scenenode.getPatch())
        return

    # Traversor class to visit child primitives of entities
    class nodeVisitor(SceneNodeVisitor):
        def pre(self, scenenode):
            # Brush?
            if scenenode.isBrush():
                processBrush(scenenode.getBrush())
            # Patch?
            elif scenenode.isPatch():
                processPatch(scenenode.getPatch())

            # Traverse all child nodes, regardless of type
            return 1

    class dataCollector(SelectionVisitor):
        fs_origin = Vector3(0,0,0)
        def visit(self, scenenode):
            if scenenode.getNodeType() == 'primitive':
                processPrimitive(scenenode)
            elif scenenode.isEntity():
                import re

                # greebo: Found an entity, this could be a func_static or similar
                # Traverse children of this entity using a new walker
                nodewalker = nodeVisitor()
                scenenode.traverse(nodewalker)

                entitynode = scenenode.getEntity()

                if not entitynode.getKeyValue("origin") == '':
                    origin = entitynode.getKeyValue("origin");
                    coords = re.findall(r'([\-\d.]+)', origin)
                    self.fs_origin = Vector3(float(coords[0]), float(coords[1]), float(coords[2]))
                    
            else:
                print('WARNING: unsupported node type selected. Skipping: ' + scenenode.getNodeType())

    # Dialog
    dialog = GlobalDialogManager.createDialog(script + 'v' + version)

    # Add an entry box and remember the handle
    fileHandle = dialog.addEntryBox("Filename:")
    dialog.setElementValue(fileHandle, GlobalRegistry.get('user/scripts/aseExport/recentFilename'))

    # Add an entry box and remember the handle
    pathHandle = dialog.addPathEntry("Save path:", True)
    dialog.setElementValue(pathHandle, GlobalRegistry.get('user/scripts/aseExport/recentPath'))

    # Add a checkbox
    centerObjectsHandle = dialog.addCheckbox("Center objects at 0,0,0 origin")
    dialog.setElementValue(centerObjectsHandle, GlobalRegistry.get('user/scripts/aseExport/centerObjects'))

    # Add another checkbox
    exportCaulkHandle = dialog.addCheckbox("Export caulked faces")
    dialog.setElementValue(exportCaulkHandle, GlobalRegistry.get('user/scripts/aseExport/exportcaulk'))

    # Add a checkbox for single entity
    checkedOrigin = 0
    if selectionInfo.entityCount == 1:
	keepOriginHandle = dialog.addCheckbox("Keep origin")
	dialog.setElementValue(keepOriginHandle, GlobalRegistry.get('user/scripts/aseExport/keepOrigin'))
        checkedOrigin = 1

    if dialog.run() == Dialog.OK:
        fullpath = dialog.getElementValue(pathHandle) + '/' + dialog.getElementValue(fileHandle)
        if not fullpath.endswith('.ase'):
            fullpath = fullpath + '.ase'

        # Save the path for later use
        GlobalRegistry.set('user/scripts/aseExport/recentFilename', dialog.getElementValue(fileHandle))
        GlobalRegistry.set('user/scripts/aseExport/recentPath', dialog.getElementValue(pathHandle))
        GlobalRegistry.set('user/scripts/aseExport/centerObjects', dialog.getElementValue(centerObjectsHandle))
        GlobalRegistry.set('user/scripts/aseExport/exportcaulk', dialog.getElementValue(exportCaulkHandle))
	if selectionInfo.entityCount == 1:	
	    GlobalRegistry.set('user/scripts/aseExport/keepOrigin',dialog.getElementValue(keepOriginHandle))
	    checkedOrigin *= dialog.getElementValue(keepOriginHandle)
        
        try:
            file = open(fullpath, 'r')
            file.close()
            prompt = GlobalDialogManager.createMessageBox('Warning', 'The file ' + fullpath + ' already exists. Do you wish to overwrite it?', Dialog.ASK)
            if prompt.run() == Dialog.YES:
                overwrite = True
            else:
                overwrite = False
        except IOError:
            overwrite = True

        if overwrite:

            # Tels: Only collect the data if we are going to export it
            walker = dataCollector()
            GlobalSelectionSystem.foreachSelected(walker)
	    
	    found_func_static_origin = walker.fs_origin
    	    
   	    #Obsttorte: check if we should keep the origin
	    if int(checkedOrigin) == 1:
		#Keep origin
                xcenter = found_func_static_origin.x()
            	ycenter = found_func_static_origin.y()
            	zcenter = found_func_static_origin.z()
		for item in geomlist:
                    for vert in item[0]:
                        vert[0] = vert[0] - xcenter
                        vert[1] = vert[1] - ycenter
                        vert[2] = vert[2] - zcenter

            # greebo: Check if we should center objects at the 0,0,0 origin
	    # Obsttorte: ... if not already kept at origin
            elif int(dialog.getElementValue(centerObjectsHandle)) == 1:
                #center objects at 0,0,0
                xlist = []
                ylist = []
                zlist = []
                for item in geomlist:
                    for vert in item[0]:
                        xlist.append(vert[0])
                        ylist.append(vert[1])
                        zlist.append(vert[2])
                xcenter=(max(xlist)+min(xlist))/2
                ycenter=(max(ylist)+min(ylist))/2
                zcenter=(max(zlist)+min(zlist))/2
                for item in geomlist:
                    for vert in item[0]:
                        vert[0] = vert[0] - xcenter
                        vert[1] = vert[1] - ycenter
                        vert[2] = vert[2] - zcenter
	    
	    # Obsttorte: merge faces with same texture
	    geomlist2 = []
	    facetlist = []
	    pointlist = []
	    for shaderNumb, shader in enumerate(shaderlist):
		for x in geomlist:
		    for i, facet in enumerate(x[1]):
			if facet[3] == shaderNumb:
			    for l in range(3):
				pointlist.append(x[0][x[1][i][l]])
				x[1][i][l] = len(pointlist)-1
			    facetlist.append(x[1][i])
		geomlist2.append([pointlist[:], facetlist[:]])
		del facetlist[:]
		del pointlist[:]
	    del geomlist[:]
	    geomlist = geomlist2
    	    
            scene = '''\t*SCENE_FILENAME "{0}"
\t*SCENE_FIRSTFRAME 0
\t*SCENE_LASTFRAME 100
\t*SCENE_FRAMESPEED 30
\t*SCENE_TICKSPERFRAME 160
\t*SCENE_BACKGROUND_STATIC 0.0000\t0.0000\t0.0000
\t*SCENE_AMBIENT_STATIC 0.0000\t0.0000\t0.0000'''.format(GlobalMap.getMapName())

            materials = str()
            for x in shaderlist:
                materials = materials + '''\t*MATERIAL {0} {{
\t\t*MATERIAL_NAME "{1}"
\t\t*MATERIAL_CLASS "Standard"
\t\t*MATERIAL_AMBIENT 0.5882\t0.5882\t0.5882
\t\t*MATERIAL_DIFFUSE 0.5882\t0.5882\t0.5882
\t\t*MATERIAL_SPECULAR 0.9000\t0.9000\t0.9000
\t\t*MATERIAL_SHINE 0.1000
\t\t*MATERIAL_SHINESTRENGTH 0.0000
\t\t*MATERIAL_TRANSPARENCY 0.0000
\t\t*MATERIAL_WIRESIZE 1.0000
\t\t*MATERIAL_SHADING Blinn
\t\t*MATERIAL_XP_FALLOFF 0.0000
\t\t*MATERIAL_SELFILLUM 0.0000
\t\t*MATERIAL_FALLOFF In
\t\t*MATERIAL_XP_TYPE Filter
\t\t*MAP_DIFFUSE {{
\t\t\t*MAP_NAME "{2}"
\t\t\t*MAP_CLASS "Bitmap"
\t\t\t*MAP_SUBNO 1
\t\t\t*MAP_AMOUNT 1.0000
\t\t\t*BITMAP "\\\\purgatory\\purgatory\\doom\\base\{2}"
\t\t\t*MAP_TYPE Screen
\t\t\t*UVW_U_OFFSET 0.0000
\t\t\t*UVW_V_OFFSET 0.0000
\t\t\t*UVW_U_TILING 1.0000
\t\t\t*UVW_V_TILING 1.0000
\t\t\t*UVW_ANGLE 0.0000
\t\t\t*UVW_BLUR 1.0000
\t\t\t*UVW_BLUR_OFFSET 0.0000
\t\t\t*UVW_NOUSE_AMT 1.0000
\t\t\t*UVW_NOISE_SIZE 1.0000
\t\t\t*UVW_NOISE_LEVEL 1
\t\t\t*UVW_NOISE_PHASE 0.0000
\t\t\t*BITMAP_FILTER Pyramidal
\t\t}}
\t}}
'''.format(shaderlist.index(x), x, x.replace('/','\\'))

            geomobjects = str()
	    
	    # Obsttorte: point-mapping to save multiple storage of the same point coordinates
	    mapping = []
	    vertMap = []
	    for x in geomlist:
		j = 1
		for count, data in enumerate(x[0]):
		    if count == 0:
			vertMap.append(0)
		    else:
			k=len(vertMap)
			for i in range(k):
			    if (x[0][i][0] == data[0] and x[0][i][1] == data[1] and x[0][i][2] == data[2]):
				vertMap.append(vertMap[i])
				break
			if len(vertMap) <= count:
			    vertMap.append(j)
			    j += 1
		mapping.append(vertMap[:])
		del vertMap[:]
		
	    # Obsttorte: same thing for CVERTICES
	    Cmapping = []
	    for x in geomlist:
		j = 1
		for count, data in enumerate(x[0]):
		    if count == 0:
			vertMap.append(0)
		    else:
			k=len(vertMap)
			for i in range(k):
			    if (x[0][i][3] == data[3] and x[0][i][4] == data[4]):
				vertMap.append(vertMap[i])
				break
			if len(vertMap) <= count:
			    vertMap.append(j)
			    j += 1
		Cmapping.append(vertMap[:])
		del vertMap[:]
		
	    j = -1
            for x in geomlist:
		
		j += 1
                # x[0] = vertices
                # vert[0] - vert[2] = vertex coords
                # vert[3] - vert[4] = texture coords
                # vert[5] - vert[7] = normal
                # x[1] = faces
                vertlist = str()
		i = 0
		Ci = 0
                for count, data in enumerate(x[0]):
		    if mapping[j][count] == i:
                    	vertlist = vertlist + '''\t\t\t*MESH_VERTEX {0}\t{1: 10.4g}\t{2: 10.4g}\t{3: 10.4g}\n'''.format(i, data[0], data[1], data[2])
			i += 1
		
                facelist = str()
                for count, data in enumerate(x[1]):
                    facelist = facelist + '''\t\t\t*MESH_FACE     {0}:  A:   {1} B:   {2} C:     {3} AB:       0 BC:    0 CA:    0\t *MESH_SMOOTHING 1 \t*MESH_MTLID {4}\n'''.format(count, mapping[j][data[0]], mapping[j][data[1]], mapping[j][data[2]], data[3])
                tvertlist = str()
                for count, data in enumerate(x[0]):
		    if Cmapping[j][count] == Ci:
                        tvertlist = tvertlist + '''\t\t\t*MESH_TVERT {0}\t{1: 10.4g}\t{2: 10.4g}\t0\n'''.format(Ci, data[3], data[4])
			Ci += 1
                tfacelist = str()
                for count, data in enumerate(x[1]):
                    tfacelist = tfacelist + '''\t\t\t*MESH_TFACE {0}\t{1}\t{2}\t{3}\n'''.format(count, Cmapping[j][data[0]], Cmapping[j][data[1]], Cmapping[j][data[2]])
                
                normals = str()
                for count, data in enumerate(x[1]):
                    normals += '''\t\t\t*MESH_FACENORMAL {0}\t{1: 10.4g}\t{2: 10.4g}\t{3: 10.4g}\n'''.format(count, x[0][data[0]][5], x[0][data[0]][6], x[0][data[0]][7]) # greebo: use first vertex normal as face normal
                    normals += '''\t\t\t\t*MESH_VERTEXNORMAL {0}\t{1: 10.4g}\t{2: 10.4g}\t{3: 10.4g}\n'''.format(mapping[j][data[0]], x[0][data[0]][5], x[0][data[0]][6], x[0][data[0]][7])
                    normals += '''\t\t\t\t*MESH_VERTEXNORMAL {0}\t{1: 10.4g}\t{2: 10.4g}\t{3: 10.4g}\n'''.format(mapping[j][data[1]], x[0][data[1]][5], x[0][data[1]][6], x[0][data[1]][7])
                    normals += '''\t\t\t\t*MESH_VERTEXNORMAL {0}\t{1: 10.4g}\t{2: 10.4g}\t{3: 10.4g}\n'''.format(mapping[j][data[2]], x[0][data[2]][5], x[0][data[2]][6], x[0][data[2]][7])

                if len(x[1]) == 0:
                    continue

                geomobjects = geomobjects + '''*GEOMOBJECT {{
\t*NODE_NAME "{0}"
\t*NODE_TM {{
\t\t*NODE_NAME "{0}"
\t\t*INHERIT_POS 0 0 0
\t\t*INHERIT_ROT 0 0 0
\t\t*INHERIT_SCL 0 0 0
\t\t*TM_ROW0 1.0000\t0.0000\t0.0000
\t\t*TM_ROW1 0.0000\t1.0000\t0.0000
\t\t*TM_ROW2 0.0000\t0.0000\t1.0000
\t\t*TM_ROW3 0.0000\t0.0000\t0.0000
\t\t*TM_POS 0.0000\t0.0000\t0.0000
\t\t*TM_ROTAXIS 0.0000\t0.0000\t0.0000
\t\t*TM_ROTANGLE 0.0000
\t\t*TM_SCALE 1.0000\t1.0000\t1.0000
\t\t*TM_SCALEAXIS 0.0000\t0.0000\t0.0000
\t\t*TM_SCALEAXISANG 0.0000
\t}}
\t*MESH {{
\t\t*TIMEVALUE 0
\t\t*MESH_NUMVERTEX {1}
\t\t*MESH_NUMFACES {2}
\t\t*MESH_VERTEX_LIST {{
{3}\t\t}}
\t\t*MESH_FACE_LIST {{
{4}\t\t}}
\t\t*MESH_NUMTVERTEX {5}
\t\t*MESH_TVERTLIST {{
{6}\t\t}}
\t\t*MESH_NUMTVFACES {7}
\t\t*MESH_TFACELIST {{
{8}\t\t}}
\t\t*MESH_NUMCVERTEX 0
\t\t*MESH_NUMCVFACES 0
\t\t*MESH_NORMALS {{
{9}\t\t}}
\t}}
\t*PROP_MOTIONBLUR 0
\t*PROP_CASTSHADOW 1
\t*PROP_RECVSHADOW 1
\t*MATERIAL_REF {10}
}}\n'''.format('mesh' + str(geomlist.index(x)), \
        i, \
        len(x[1]), \
        vertlist, \
        facelist, \
        Ci, \
        tvertlist, \
        len(x[1]), \
        tfacelist, \
        normals, \
        x[1][0][3]) # material reference from first face

                data = '''*3DSMAX_ASCIIEXPORT\t200
*COMMENT "{0} v{1}"
*SCENE {{
{2}
}}
*MATERIAL_LIST {{
\t*MATERIAL_COUNT {3}
{4}}}
{5}'''.format(script, version, scene, len(shaderlist), materials, geomobjects)

                # Write the compiled data to the output file
                file = open(fullpath, 'w')
                file.write(data)
                file.close()

# __executeCommand__ evaluates to true after DarkRadiant has successfully initialised
if __executeCommand__:
    execute()
