#----------------------------------------------------------------
# $Id: //src/racer/Mesh2DOF.py#21 $
# $Date: 2005/06/08 $
#----------------------------------------------------------------
# This file may be copied.  However, if you make improvements,
# please send me a copy.
#
# Brent Burton <brentb@relro.net>
# Originally released: July 2003
# RaceSimCentral: vizor_atx
#----------------------------------------------------------------
#
# Convert a Blender NMesh object to a DOF
# file.  This module needs the DOFfile module.
#

import string
import Blender
from Blender import Types, Object, NMesh, Material
import DOFfile
import VertOpt

#---------------- module version info
version = 1.0

#----------------
# PRIVATE _DefineMaterial()
def _DefineMaterial(mesh, matidx, matTexNames):
    """
    Return a DOFfile.MAT0 object representing this Blender material.
    """

    # Now convert the mesh material to MAT0 parts...
    mat = mesh.materials[matidx]

    texfilename = matTexNames[matidx]

    mat0 = DOFfile.MAT0()

    mhdr = DOFfile.MHDR()
    mhdr.setName(mat.name)
    mat0.setMHDR(mhdr)

    mcol = DOFfile.MCOL()
    mcol.setAmbient([mat.R*mat.amb, mat.G*mat.amb, mat.B*mat.amb, mat.alpha])
    mcol.setDiffuse([mat.R, mat.G, mat.B, mat.alpha])
    mcol.setSpecular([mat.specCol[0]*mat.spec, mat.specCol[1]*mat.spec, mat.specCol[2]*mat.spec, mat.alpha])
    mcol.setEmission([mat.R*mat.emit, mat.G*mat.emit, mat.B*mat.emit, mat.alpha])
    mcol.setShininess(mat.hard)
    mat0.setMCOL(mcol)

    mcfl = DOFfile.MCFL()
    mat0.setMCFL(mcfl)

    muvw = DOFfile.MUVW()
    muvw.setUVOffset([0.0, 0.0])
    muvw.setUVTiling([1.0, 1.0])
    muvw.setAngle(0.0)
    muvw.setBlur(1.0)
    muvw.setBlurOffset(0.0)
    mat0.setMUVW(muvw)

    mtra = DOFfile.MTRA()
    mtra.setTransparency(1 - mat.alpha) # unused field, used for BLEND_CONST I guess.
    if mat.alpha > 0.99:
        mtra.setBlendmode(0)            # off
    else:
        mtra.setBlendmode(1)            # 1=BLEND_SRC_ALPHA; 2=BLEND_CONST(unused)
    mat0.setMTRA(mtra)

    mtex = DOFfile.MTEX()
    mtex.append(texfilename)
    mat0.setMTEX(mtex)
    return mat0


#----------------
# PRIVATE _DefineBogusMaterial()
def _DefineBogusMaterial(matidx, matTexNames):
    """
    Return a DOFfile.MAT0 object for a texture's name.
    """

    texfilename = matTexNames[matidx]
    # Make material name by stripping off texture filename extension
    matname = string.join(string.split(texfilename, ".")[0:-1], "")

    mat0 = DOFfile.MAT0()

    mhdr = DOFfile.MHDR()
    mhdr.setName(matname)
    mat0.setMHDR(mhdr)

    mcol = DOFfile.MCOL()
    mcol.setAmbient([0.5, 0.5, 0.5, 1.0])
    mcol.setDiffuse([0.8, 0.8, 0.8, 1.0])
    mcol.setSpecular([0.0, 0.0, 0.0, 1.0])
    mcol.setEmission([0.0, 0.0, 0.0, 1.0])
    mcol.setShininess(128.0)
    mat0.setMCOL(mcol)

    mcfl = DOFfile.MCFL()
    mat0.setMCFL(mcfl)

    muvw = DOFfile.MUVW()
    muvw.setUVOffset([0.0, 0.0])
    muvw.setUVTiling([1.0, 1.0])
    muvw.setAngle(0.0)
    muvw.setBlur(1.0)
    muvw.setBlurOffset(0.0)
    mat0.setMUVW(muvw)

    mtra = DOFfile.MTRA()
    mtra.setTransparency(0.0)           # unused
    mtra.setBlendmode(0)                # off
    mat0.setMTRA(mtra)

    mtex = DOFfile.MTEX()
    mtex.append(texfilename)
    mat0.setMTEX(mtex)
    return mat0


# Object coordinates are local.  Each object's reference
# point can be moved in Blender to translate the entire object.
# Further, scaling can take place.  Sooo, adjust XYZ
# coords by appropriate scalings and translations.
# The scale arg is a final inverse-modeling scale.
# TODO: do matrix rotations
def _xformVertex(v, bobj, scale=1.0):
    x = (v[0] * bobj.SizeX + bobj.LocX) * scale
    y = (v[1] * bobj.SizeY + bobj.LocY) * scale
    z = (v[2] * bobj.SizeZ + bobj.LocZ) * scale
    # the (x, z, -y) is intentional as it converts
    # the modeler (3dsmax/blender) coordinate system
    # to OpenGL's.
    return [x, z, -y]

def _xformNormal(n):
    # Ditto on the x,z,-y thing here
    return [n[0], n[2], -n[1]]

def _getVColor(nmcol):
    # NMCol input
    r = nmcol.r / 255.0
    g = nmcol.g / 255.0
    b = nmcol.b / 255.0
    return [r, g, b]

#----------------
# PRIVATE ConvertMeshToDOF():
def _ConvertMeshToDOF(obj, scale=1.0):
    mesh = obj.getData()
    # Create the DOF.  Then add a Material
    # and the geometry
    dof1 = DOFfile.DOF1()

    # If this is set to 1 AND a mesh doesn't have a material
    # defined, vertex colors are written out to the DOF to
    # provide coloring.  This disables Racer's lighting
    # calculations on the meshes that have vertex coloring.
    # However, if it is forced to 0 OR the mesh has materials
    # defined, no vertex colors are exported and Racer shades the
    # mesh with lighting, based on the material's properties such
    # as reflectance, specular color, ambient color and emissive
    # color.
    exportVertColors = 0

    # Preprocess materials to size lists
    numMats = len(mesh.materials)
    if numMats > 0:
        exportVertColors = 0
        matFaces = []                       # array of empty lists
        matTexNames = []
        for i in range(numMats):
            matFaces.append([])             # add another empty list
            matTexNames.append(None)        # add placeholder

        # Now figure out which faces go with which materials.
        lastMatIndex = -1
        for i in range(len(mesh.faces)):
            matidx = mesh.faces[i].materialIndex
            matFaces[matidx].append(i)
            if lastMatIndex != matidx:
                if mesh.faces[i].image is None:
                    print "*** Face %d has no texture!!"%(i)
                    continue
                matTexNames[matidx] = string.lower(mesh.faces[i].image.getName())
                lastMatIndex = matidx

        # Finally define the actual mats since texnames are now known
        for i in range(numMats):
            mat0 = _DefineMaterial(mesh, i, matTexNames)
            dof1.addMAT0(mat0)

    else:
        # No materials defined.  The intent is to use Racer shaders
        # so iterate through the faces and see which textures have
        # been applied.  At least that much should be defined...
        lastMatIndex = -1
        texNamesIndex = {}
        matFaces = []                   # array of empty lists
        matTexNames = []
        for i in range(len(mesh.faces)):
            if mesh.faces[i].image is None:
                print "*** Face %d has no texture!!"%(i)
                continue
            texname = string.lower(mesh.faces[i].image.getName())
            # Map texture name to an index; define one if not mapped.
            if texname not in texNamesIndex:
                if len(texNamesIndex.keys()) == 0: # if nothing in it yet
                    idx = 0     # start index at zero
                else:
                    idx = max(texNamesIndex.values()) + 1
                matFaces.append([])
                matTexNames.append(None)
                texNamesIndex[texname] = idx

            matidx = texNamesIndex[texname]
            matFaces[matidx].append(i)  # append face number to this material's list
            if lastMatIndex != matidx:
                matTexNames[matidx] = texname
                lastMatIndex = matidx

        # Define bogus materials for these texture names
        for i in range(len(matTexNames)):
            mat0 = _DefineBogusMaterial(i, matTexNames)
            dof1.addMAT0(mat0)

    #-- Materials are now defined.

    # Init some counters, and write the geometry out:
    verts = mesh.verts
    numOriginalVerts = len(verts)
    totalIndexCount = 0
    totalNumTris = 0
    totalOptIndexCount = 0

    # For each material, make a new geob and process its faces
    for i in range(len(matFaces)):

        if len(matFaces[i]) == 0:       # no faces for material i
            continue

        gob1 = DOFfile.GOB1()
        dof1.addGOB1(gob1)

        gob1.setGHDR( DOFfile.GHDR(0,0,i) )

        # Iterate over the faces.  Vertices have XYZ and UV data
        # separate in Blender; faces pick XYZ and UV values via
        # indices, and in Racer the XYZ/UV needs to be combined.
        # Since a single XYZ value can have numerous UV values
        # (in the case of corner (i.e. shared) vertices), the XYZ
        # values must be duplicated for as many faces that share them...
        dofverts   = DOFfile.VERT()
        dofvcols   = DOFfile.VCOL()
        dofuvs     = DOFfile.TVER()
        dofnorms   = DOFfile.NORM()
        dofindices = DOFfile.INDI()
        dofburst   = DOFfile.BRST()

        origIndexCount = 0
        optIndexCount = -1
        numTris = 0
        vo = VertOpt.VO()               # create Vertex Optimizer
        for faceIndex in matFaces[i]:
            face = mesh.faces[faceIndex]
            corners = len(face.v)
            if corners >= 3:                # add 1st tri: 0,1,2
                
                # XYZ, Norm, UV
                xyz    = (x,y,z)    = verts[face.v[0].index].co
                uv     = (u,v)      = face.uv[0]
                normal = (xn,yn,zn) = verts[face.v[0].index].no

                idx0 = vo.addVertex((x,y,z,u,v,xn,yn,zn))
                if idx0 > optIndexCount:
                    optIndexCount = idx0
                    dofverts.append(_xformVertex(xyz, obj, scale))
                    dofnorms.append(_xformNormal(normal))
                    dofvcols.append(_getVColor(face.col[0]))
                    dofuvs.append(uv)

                xyz    = (x,y,z)    = verts[face.v[1].index].co
                normal = (xn,yn,zn) = verts[face.v[1].index].no
                uv     = (u,v)      = face.uv[1]
                idx1 = vo.addVertex((x,y,z,u,v,xn,yn,zn))
                if idx1 > optIndexCount:
                    optIndexCount = idx1
                    dofverts.append(_xformVertex(xyz, obj, scale))
                    dofnorms.append(_xformNormal(normal))
                    dofvcols.append(_getVColor(face.col[1]))
                    dofuvs.append(uv)

                xyz    = (x,y,z)    = verts[face.v[2].index].co
                normal = (xn,yn,zn) = verts[face.v[2].index].no
                uv     = (u,v)      = face.uv[2]
                idx2 = vo.addVertex((x,y,z,u,v,xn,yn,zn))
                if idx2 > optIndexCount:
                    optIndexCount = idx2
                    dofverts.append(_xformVertex(xyz, obj, scale))
                    dofnorms.append(_xformNormal(normal))
                    dofvcols.append(_getVColor(face.col[2]))
                    dofuvs.append(uv)

                origIndexCount += 3
                dofindices.append(idx0)
                dofindices.append(idx1)
                dofindices.append(idx2)
                numTris += 1

            if corners == 4:                # add 2nd tri: 2,3,0
                xyz    = (x,y,z)    = verts[face.v[3].index].co
                normal = (xn,yn,zn) = verts[face.v[3].index].no
                uv     = (u,v)      = face.uv[3]
                idx3 = vo.addVertex((x,y,z,u,v,xn,yn,zn))
                if idx3 > optIndexCount:
                    optIndexCount = idx3
                    dofverts.append(_xformVertex(xyz, obj, scale))
                    dofnorms.append(_xformNormal(normal))
                    dofvcols.append(_getVColor(face.col[3]))
                    dofuvs.append(uv)

                origIndexCount += 1
                dofindices.append(idx2)     # reuse
                dofindices.append(idx3)
                dofindices.append(idx0)     # reuse
                numTris += 1

        del vo

        dofburst.addBurst(0, numTris * 9, 0, 3)
        totalNumTris += numTris
        totalIndexCount += origIndexCount
        totalOptIndexCount += optIndexCount

        # Done.  Pass the arrays to the geom object.
        gob1.setVERT(dofverts)
        if exportVertColors:  gob1.setVCOL(dofvcols)
        gob1.setNORM(dofnorms)
        gob1.setTVER(dofuvs)
        gob1.setINDI(dofindices)
        gob1.setBRST(dofburst)
        # End of faces for this material

    return dof1, numOriginalVerts, totalIndexCount, totalOptIndexCount, totalNumTris


#----------------
# PUBLIC ExportMesh()
def ExportMesh(obj, directory, scale=1.0):
    """Exports a Blender NMesh object to a DOF file in 'directory'."""

    (dof1, vin, vout, vopt, tris) = _ConvertMeshToDOF(obj, scale)

    string.replace(directory, "\\", "/")
    lastChar = directory[len(directory)-1]
    if lastChar != "/": directory += "/"

    filename = string.lower(directory + string.replace(obj.name, ".", "") + ".dof")
    outf = file(filename, "wb")
    dof1.write(outf)
    outf.flush()
    outf.close()
    return (vin, vout, vopt, tris)

#----------------
# PUBLIC ExportLayer()
# Scale is an inverse-modeling factor.
# I.e., if the modeling in Blender is done at 1:10, scale would be 10.0.
def ExportLayer(layer, directory, scale=1.0):
    """Exports all mesh objects from a particular layer.
    If layer==-1, all mesh objects are exported.
    The resulting DOF files are stored in 'directory'.
    """
    numExported = 0
    totalVin = 0
    totalVout = 0
    totalVopt = 0
    totalTris = 0
    for obj in Blender.Object.Get():
        if layer == -1 or layer == obj.Layer:
            if type(obj.data) == Types.NMeshType:
                print "Exporting mesh " +  obj.name
                (vin, vout, vopt, tris) = ExportMesh(obj, directory, scale)
                print "---> Vin: %d  Vout: %d  Vopt: %d Tris: %d" % (vin, vout, vopt, tris)
                totalVin += vin
                totalVout += vout
                totalVopt += vopt
                totalTris += tris
                numExported += 1
    print "%d meshes exported into %s" % (numExported, directory)
    print "Totals: Vin: %d  Vout: %d  Vopt: %d Tris: %d" % (totalVin, totalVout, totalVopt, totalTris)

# EOF
