#!BPY

"""
Name: 'P3D Export (.p3d)...'
Blender: 242
Group: 'Export'
Tip: 'Export a (.p3d) '
"""

__author__ = "Lukas Pinkowski"
__url__ = ("http://bitbucket.org/vektorboson/blender_ofp")
__version__ = "1.0"

__bpydoc__ = """\
This script exports P3D MLOD from Blender.
"""

import Blender
import struct
import math

SETTING_EXCLUDE_LAST_LAYER = False

def draw():
    Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
    Blender.Draw.Toggle("Exclude last layer",1,10,20,300,20,0,"includes/excludes objects from last layer when exporting")
    Blender.Draw.PushButton("Save...", 2,10,50,300,20,"save...")
    
def event(evt,val):
    if evt == Blender.Draw.ESCKEY:
        Blender.Draw.Exit()

def button(evt):
    global SETTING_EXCLUDE_LAST_LAYER
    if evt == 1:
        SETTING_EXCLUDE_LAST_LAYER = not SETTING_EXCLUDE_LAST_LAYER
    elif evt == 2:
        # Execute the script
        Blender.Draw.Exit()
        Blender.Window.FileSelector(export_p3d, 'Export P3D')

Blender.Draw.Register(draw,event,button)

def export_p3d(path):
   print "Start exporting"
   Blender.Window.WaitCursor(1)
   
   scn = Blender.Scene.GetCurrent()
   # enumerate objects by layer (only first layer where it occurs
   # counts)
   layers = [None for x in xrange(20)]
   
   for o in scn.objects:
      if o.layers and o.getType() == 'Mesh':
         if not (SETTING_EXCLUDE_LAST_LAYER and o.layers[0]==20):
            layers[o.layers[0]-1] = o
            print "added object for layer %s" % (o.layers[0]-1)
   
   p3d = P3DFile()
   res = 0.0
   
   TEXMODE = Blender.Mesh.FaceModes['TEX']
   
   for L in layers:
      if L:
            # create a new LOD
            lod = p3d.createLOD()
            lod.resolution = res
            res += 1.0
            
            mesh = L.getData(mesh=True)
            mat = L.matrixLocal.copy()
            m_rot = mat.rotationPart()
            # I need to use the inverse rotation, but the scale needs to be applied separately
            m_rot.invert()
            m_scale = mat.scalePart()
            # it seems, Blender uses the square of the scale... why???
            m_scale = Blender.Mathutils.Vector(m_scale.x*m_scale.x, m_scale.y*m_scale.y, m_scale.z*m_scale.z)
            loc = Blender.Mathutils.Vector(L.loc)
            
            lod.vertices = []
            for v in mesh.verts:
                tv = m_rot * Blender.Mathutils.Vector(m_scale.x*v.co.x, m_scale.y*v.co.y, m_scale.z*v.co.z) + loc
                lod.vertices.append(Vert((tv.x, tv.z, tv.y)))
            lod.normals = [Normal((v.no.x, v.no.z, v.no.y)) for v in mesh.verts]
            
            lod.polygons = []
            for F in mesh.faces:
               indices = [v.index for v in F.verts]
               texture = ""
               print "F.mat: %s, len(mesh.mat): %s" % (F.mat, len(mesh.materials))
               if F.mat < len(mesh.materials):
                  texture = mesh.materials[F.mat].getName()
               
               if mesh.faceUV:
                  uv = [(st[0], 1.0-st[1]) for st in F.uv]
               else:
                  uv = [(0.0, 0.0) for x in F.verts]
               lod.polygons.append(Face(indices, indices, texture, uv))
            
            for E in mesh.edges:
               if E.flag & Blender.Mesh.EdgeFlags.SHARP:
                  lod.sharpEdges.append( (E.v1.index, E.v2.index) )
               
            # vertex groups -> named selections
            # only vertices will be selected, not faces!
            print "Exporting selections"
            for grp in mesh.getVertGroupNames():
               print "Transforming vertex group '%s'" % grp
               selection = SP3X_Selection([], [0 for x in lod.polygons])
               selection.vertices = [0.0 for x in lod.vertices]
               for index, weight in mesh.getVertsFromGroup(grp, True):
                  if weight > 1.0:
                        print "In vertex group '%s' bogus weight '%s' for vertex index %d" % (grp, weight, index)
                  selection.vertices[index] = weight
               lod.selections[grp] = selection
   
   print "Writing p3d file to '%s'" % path
   p3d.writeToPath(path)
   
   Blender.Window.WaitCursor(0)
   print "End exporting"


VIEW_GUNNER = 0x447a0000 # 1.0e3f
VIEW_PILOT = 0x44898000 # 1.1e3f
VIEW_CARGO = 0x44960000 # 1.2e3f
STENCIL_SHADOW = 0x461c4000 # 1.0e4
STENCIL_SHADOW_2 = 0x461c6800 # 1.001e4
SHADOW_VOLUME = 0x462be000 # 1.1e4
SHADOW_VOLUME_2 = 0x462c0800 # 1.101e4
GEOMETRY = 0x551184e7 # 1.0e13f
MEMORY = 0x58635fa9 # 1.0e15f
LANDCONTACT = 0x58e35fa9 # 2.0e15f
ROADWAY = 0x592a87bf # 3.0e15f
PATHS = 0x59635fa9 # 4.0e15f
HITPOINTS = 0x598e1bca # 5.0e15f
VIEW_GEOMETRY = 0x59aa87bf # 6.0e15f
FIREGEOMETRY = 0x59c6f3b4 # 7.0e15f
VIEW_CARGO_GEOMETRY = 0x59e35fa9 # 8.0e15f
VIEW_CARGO_FIREGEOMETRY = 0x59ffcb9e # 9.0e15f
VIEW_COMMANDER = 0x5a0e1bca # 1.0e16f
VIEW_COMMANDER_GEOMETRY = 0x5a1c51c4 # 1.1e16f
VIEW_COMMANDER_FIREGEOMETRY = 0x5a2a87bf # 1.2e16f
VIEW_PILOT_GEOMETRY = 0x5a38bdb9 # 1.3e16f
VIEW_PILOT_FIREGEOMETRY = 0x5a46f3b4 # 1.4e16f
VIEW_GUNNER_GEOMETRY = 0x5a5529af # 1.5e16f
VIEW_GUNNER_FIREGEOMETRY = 0x5a635fa9 # 1.6e16f
SUB_PARTS = 0x5a7195a4 # 1.7e16f

LodResolutionTable = {
    'VIEW_GUNNER': 0x447a0000, 
    'VIEW_PILOT': 0x44898000, 
    'VIEW_CARGO': 0x44960000, 
    'STENCIL_SHADOW': 0x461c4000, 
    'STENCIL_SHADOW_2': 0x461c6800, 
    'SHADOW_VOLUME': 0x462be000, 
    'SHADOW_VOLUME_2': 0x462c0800, 
    'GEOMETRY': 0x551184e7, 
    'MEMORY': 0x58635fa9, 
    'LANDCONTACT': 0x58e35fa9, 
    'ROADWAY': 0x592a87bf, 
    'PATHS': 0x59635fa9, 
    'HITPOINTS': 0x598e1bca, 
    'VIEW_GEOMETRY': 0x59aa87bf, 
    'FIREGEOMETRY': 0x59c6f3b4, 
    'VIEW_CARGO_GEOMETRY': 0x59e35fa9, 
    'VIEW_CARGO_FIREGEOMETRY': 0x59ffcb9e, 
    'VIEW_COMMANDER': 0x5a0e1bca, 
    'VIEW_COMMANDER_GEOMETRY': 0x5a1c51c4, 
    'VIEW_COMMANDER_FIREGEOMETRY': 0x5a2a87bf, 
    'VIEW_PILOT_GEOMETRY': 0x5a38bdb9, 
    'VIEW_PILOT_FIREGEOMETRY': 0x5a46f3b4, 
    'VIEW_GUNNER_GEOMETRY': 0x5a5529af, 
    'VIEW_GUNNER_FIREGEOMETRY': 0x5a635fa9,
    'SUB_PARTS': 0x5a7195a4
}



class Vert:
    """
    A Vert is a vertex, which is a point in space, most of the
    time a point of a triangle/quad.
    """
    def __init__(self, coords=[0.0,0.0,0.0], flags=0):
        self.xyz = list(coords)
        self.flags = flags
        
    def __getitem__(self, index):
        return self.xyz[index]
        
    def __setitem__(self, index, value):
        self.xyz[index] = value


class Normal:
    """
    Normals are used for lighting calculation
    """
    def __init__(self, coords=[0.0,1.0,0.0] ):
        self.xyz = list(coords)
        
    def __getitem__(self, index):
        return self.xyz[index]
        
    def __setitem__(self, index, value):
        self.xyz[index] = value


class Face:
    """
    Faces are either triangles or quads; they have indices into a vertex-
    and normal-array, specify the UV-coordinates, the texture and some
    special rendering flags.
    """
    def __init__(self, vertexIndices=[], normalIndices=[], texture="", uv=[], flags=0, material=""):
        """
        Creates a face (triangle or quad); vertexIndices and normalIndices are
        list of indices that point to a specific vertex and normal list.
        Texture is just the relative path to the textures.
        uv is a list of UV-pairs (the list should be either of length 3 or 4).
        flags is an unsigned integer representing some bit flags for 
        the face (for example 'Billboard'-flag)
        """
        self.vertexIndices = vertexIndices if vertexIndices else []
        self.normalIndices = normalIndices if normalIndices else []
        self.texture = texture # relative path
        self.material = material
        self.uv = uv if uv else [] # a list of UV: [ (0, 0), (1, 0), (0, 1), (1, 1) ]
        self.flags = flags
        
    def calcArea(self, vertices):
        """
        Calculates the area used by a specific face (with indices into the
        vertices array). The area is the half magnitude of the triangles
        normal vector (quads are split into two triangles).
        """
        def sub(a, b):
            return (b[0]-a[0], b[1]-a[1], b[2]-a[2])
        def cross(b, c):
            return (b[1]*c[2]-b[2]*c[1], b[2]*c[0]-b[0]*c[2], b[0]*c[1]-b[1]*c[0])
        def magnitude(v):
            return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
        
        v0 = vertices[self.vertexIndices[0]].xyz
        v1 = vertices[self.vertexIndices[1]].xyz
        v2 = vertices[self.vertexIndices[2]].xyz
        area = math.fabs(0.5 * magnitude(cross(sub(v0, v1), sub(v0, v2))))
        
        if len(self.vertexIndices) == 4:
            v3 = vertices[self.vertexIndices[3]].xyz
            area += math.fabs(0.5 * magnitude(cross(sub(v3, v1), sub(v3, v2))))
            
        return area

        
        
def weightToByte(weight):
    if weight < 0.004: return 0
    elif weight >= 1.0: return 1
    else: return int(256 - 255*weight)

def byteToWeight(byte):
    if byte == 0: return 0.0
    else: return (256-byte)/255.0

def boolToSelected(val):
    return 1 if val else 0
        
        
class SP3X_Selection:
    """
    Named selections give names along with weights to a set of vertices and faces.
    This is the SP3X (OFP) version
    """
    def __init__(self, vertices, faces):
        self.verticesBytes = vertices # original byte version
        self.facesBytes = faces # original byte version
        
        self.vertices = [byteToWeight(x) for x in vertices] # vertex weights, float version (1.0 = 100%)
        self.faces = faces # list of True or False
        
    def toP3DM(self):
        pass

class P3DM_Selection:
    """
    This is the P3DM (ArmA) version of Named Selections
    """
    def __init__(self, vertices, faces):
        self.verticesBytes = vertices # original byte version
        self.facesBytes = faces # original byte version
        
        self.vertices = [byteToWeight(x) for x in vertices]
        self.faces = faces
        
    def toSP3X(self):
        pass


class LOD:
    """
    The LOD holds all relevant information, like vertices, faces, normals.
    """
    def __init__(self):
        self.type = 'SP3X'
        self.vertices = []
        self.normals = []
        self.polygons = []
        self.mass = []
        self.properties = {} # named properties
        self.selections = {} # named selections (weights...)
        self.resolution = 0.0 # resolution
        self.sharpEdges = [] # list of vertex tuples...
        self.uvset = {} # UV sets in P3DM (ArmA MLOD)
        self.flags = 0
        
        
    def setSP3X(self):
        """
        Set LOD-type to SP3X
        """
        self.type = 'SP3X'
        
        
    def setP3DM(self):
        """
        Set LOD-type to P3DM
        """
        self.type = 'P3DM'
        
        
    def convertToSP3X(self):
        """
        Convert this LOD to SP3X
        """
        if self.type == 'P3DM':
            self.type = 'SP3X'
        else:
            pass
            
            
    def convertToP3DM(self):
        """
        Convert this LOD to P3DM
        """
        if self.type == 'SP3X':
            self.type = 'P3DM'
            pass
        else:
            pass
        
        
class P3DParseError:
    def __init__(self, value):
        self.value = value
        
    def __str__(self):
        return repr(self.value)


def getZString(file):
    """
    Read a 0-terminated string from file-like object;
    """
    ret = ""
    c = struct.unpack("c",  file.read(1))[0]
    while c != '\0':
        ret = ret + c
        c = struct.unpack("c",  file.read(1))[0]
    return ret
    
        
class P3DFile:
    def __init__(self):
        self.lods = []
        self.OdolEx = False # if this P3D was made by OdolEx
        
    def createLOD(self):
        """
        Create and add a new LOD
        """
        newlod = LOD()
        self.lods.append(newlod)
        return newlod
        
    def convertToOFP(self):
        for L in self.lods:
            L.convertToSP3X()
            
    def convertToArmA(self):
        for L in self.lods:
            L.convertToP3DM()
        
        
    def writeToPath(self, path):
        """
        Writes this P3D to a specific path.
        """
        file = open(path, "wb")
        self.writeToFile(file, False)
        file.close()
        
        
    def writeToFile(self, file, close=False, defaultPath="data\\"):
        """
        Writes this MLOD to a file-like object
        """
        file.write("MLOD")
        file.write(struct.pack("i", 0x00000101))
        file.write(struct.pack("i", len(self.lods)))
        for lod in self.lods:
            if lod.type == 'SP3X':
                self.writeSP3X(file, lod)
            elif lod.type == 'P3DM':
                self.writeP3DM(file, lod)
            else:
                assert(False)
            
        # at the very last: the default path:
        if defaultPath:
            file.write(struct.pack("32s", defaultPath))
        
        if close:
            file.close()
    # end of writeToFile
    
    
    def writeSP3X(self, file, lod):
        """
        Writes OFP-style 'SP3X' Lod
        """
        file.write("SP3X")
        file.write( struct.pack("i", 0x0000001c ) )
        file.write( struct.pack("i", 0x00000099 ) )
        
        # now vertex count, normal count, polycount and lod flags
        file.write(struct.pack("i", len(lod.vertices)))
        file.write(struct.pack("i", len(lod.normals)))
        file.write(struct.pack("i", len(lod.polygons)))
        file.write(struct.pack("i", lod.flags))
        
        # write vertex data
        for v in lod.vertices:
            file.write( struct.pack("fffI", v.xyz[0], v.xyz[1], v.xyz[2], v.flags) )
            
        # write normal data
        for n in lod.normals:
            file.write( struct.pack("fff", n.xyz[0], n.xyz[1], n.xyz[2]) )
            
        # write polygon data...
        # polygon structure is:
        # 32byte zero-terminated string
        # 4byte  triangle or quad (3 or 4)
        # 4 * 16byte  iiff (vertex+normal index, uv float)
        # 4byte face flags
        for p in lod.polygons:
            file.write( struct.pack("32si", p.texture, len(p.vertexIndices)) )
            file.write( struct.pack("iiffiiffiiff", p.vertexIndices[0], p.normalIndices[0], p.uv[0][0], p.uv[0][1], p.vertexIndices[1], p.normalIndices[1], p.uv[1][0], p.uv[1][1], p.vertexIndices[2], p.normalIndices[2], p.uv[2][0], p.uv[2][1]) )
            if len(p.vertexIndices) == 3:
                file.write( struct.pack("iiff", 0, 0, 0.0, 0.0) )
            else:
                file.write( struct.pack("iiff", p.vertexIndices[3], p.normalIndices[3], p.uv[3][0], p.uv[3][1]) )
            file.write( struct.pack("I", p.flags) )
                
        # now comes all the fun stuff, the tags...
        file.write( "TAGG" )
        
        # check for sharp edges:
        if lod.sharpEdges:
            file.write( struct.pack("64si", "#SharpEdges#", 8 * len(lod.sharpEdges)) )
            # the size is 2 * 4 * len(sharpEdges)
            for se in lod.sharpEdges:
                file.write( struct.pack("ii", se[0], se[1]) )
            
        # now the properties
        if lod.properties:
            for key, value in lod.properties.iteritems():
                file.write(struct.pack("64si", "#Property#", 128))
                file.write(struct.pack("64s64s", key, value))
            
        # if we're in a geometry LOD, we need to write the mass values, too!
        if lod.resolution == GEOMETRY and lod.mass:
            file.write( struct.pack("64si", "#Mass#", 4 * len(lod.mass) ) )
            for m in lod.mass:
                file.write( struct.pack("f", m) )
            
        # now the selections
        for name, sel in lod.selections.iteritems():
            assert( len(sel.vertices) == len(lod.vertices) )
            assert( len(sel.faces) == len(lod.polygons) )
            file.write( struct.pack("64si", name, len(lod.vertices) + len(lod.polygons)) )
            # the size is 1 * len(vertices) + 1 * len(polygons)
            for v in sel.vertices:
                file.write( struct.pack("B", weightToByte(v)) )
            for p in sel.faces:
                file.write( struct.pack("B", boolToSelected(p)) )
                
            
        # end the Tags:
        file.write( struct.pack("64si", "#EndOfFile#", 0) )
        
        # last the Lod resolution:
        if isinstance(lod.resolution, float):
            file.write(struct.pack("f", lod.resolution))
        elif isinstance(lod.resolution, int):
            file.write(struct.pack('i', lod.resolution))
        else:
            raise P3DParseError('lod.resolution is neither float nor int!')
        
        
    def writeP3DM(self, file, lod):
        """
        Writes ArmA-style 'P3DM' Lod
        """
        file.write("P3DM")
        file.write( struct.pack("i", 0x0000001c ) )
        file.write( struct.pack("i", 0x00000100 ) )
        
        # now vertex count, normal count, polycount and lod flags
        file.write( struct.pack("i", len(lod.vertices)) )
        file.write( struct.pack("i", len(lod.normals)) )
        file.write( struct.pack("i", len(lod.polygons)) )
        file.write( struct.pack("i", 0) )
        
        # write vertex data
        for v in lod.vertices:
            file.write( struct.pack("fffI", v.xyz[0], v.xyz[1], v.xyz[2], v.flags) )
            
        # write normal data
        for n in lod.normals:
            file.write( struct.pack("fff", n.xyz[0], n.xyz[1], n.xyz[2]) )
            
        # write polygon data...
        # polygon structure is:
        # 4byte  triangle or quad (3 or 4)
        # 4 * 16byte  iiff (vertex+normal index, uv float)
        # 4byte face flags
        # 2 * zero-terminated string (texture and material)
        for p in lod.polygons:
            file.write(struct.pack('i', len(p.vertexIndices)))
            file.write(
                struct.pack("iiffiiffiiff", 
                    p.vertexIndices[0], p.normalIndices[0], p.uv[0][0], p.uv[0][1], 
                    p.vertexIndices[1], p.normalIndices[1], p.uv[1][0], p.uv[1][1], 
                    p.vertexIndices[2], p.normalIndices[2], p.uv[2][0], p.uv[2][1]))
            if len(p.vertexIndices) == 3:
                file.write(struct.pack("iiff", 0, 0, 0.0, 0.0))
            else:
                file.write(struct.pack("iiff", p.vertexIndices[3], p.normalIndices[3], p.uv[3][0], p.uv[3][1]))
            file.write(struct.pack("I", p.flags))
            file.write(p.texture + '\x00' if p.texture else '\x00')
            file.write(p.material + '\x00' if p.material else '\x00')
                
        # now comes all the fun stuff, the tags...
        file.write( "TAGG" )
        
        # check for sharp edges:
        if lod.sharpEdges:
            file.write(struct.pack("B", 1))
            file.write('#SharpEdges\x00')
            file.write(struct.pack('i', 8*len(lod.sharpEdges)))
            # the size is 2 * 4 * len(sharpEdges)
            for se in lod.sharpEdges:
                file.write( struct.pack("ii", se[0], se[1]) )
            
        # now the properties
        if lod.properties:
            for key, value in lod.properties.iteritems():
                file.write(struct.pack("B", 1))
                file.write('#Property#\x00')
                file.write(struct.pack("i", 128))
                file.write(struct.pack("64s64s", key, value))
            
        # if we're in a geometry LOD, we need to write the mass values, too!
        if lod.resolution == LOD_GEOMETRY and lod.mass:
            file.write(struct.pack("B", 1))
            file.write('#Mass#\x00')
            file.write(4 * len(lod.mass))
            for m in lod.mass:
                file.write( struct.pack("f", m) )
            
        # now the selections
        for name, sel in lod.selections.iteritems():
            file.write(struct.pack("B", 1))
            file.write(name + '\x00')
            file.write(struct.pack('i', len(lod.vertices) + len(lod.polygons)))
            for v in lod.vertexSelected:
                file.write( struct.pack("B", weightToByte(v)) )
            for p in lod.faceSelected:
                file.write( struct.pack("B", boolToSelected(p)) )
                
        # TODO: implement UV set
        if lod.uvset:
            file.write(lod.uvset)
                
        # end the Tags:
        file.write(struct.pack("B", 1))
        file.write('#EndOfFile#\x00')
        file.write(struct.pack('i', 0))
        
        # last the Lod resolution:
        if isinstance(lod.resolution, float):
            file.write(struct.pack("f", lod.resolution))
        elif isinstance(lod.resolution, int):
            file.write(struct.pack('i', lod.resolution))
        else:
            raise P3DParseError('lod.resolution is neither float nor int!')
        
    
    
    def readFromPath(self, path):
        """
        Read a P3D from a path (calls then readFromFile)
        """
        file = open(path, "rb")
        self.readFromFile(file, False)
        file.close()
    
        
    def readFromFile(self, file, close=False):
        """
        Read a P3D from a file-like object (if close is set, then file-object is closed).
        """
        fourcc = struct.unpack("4s", file.read(4))[0]
        if fourcc == 'ODOL':
            raise P3DParseError("File is in ODOL-format (ODOL to MLOD not implemented yet)!")
        elif fourcc != "MLOD":
            raise P3DParseError("File is not a MLOD file!")
            
        vers = struct.unpack("i", file.read(4))[0]
        if vers == 0x3f8ccccd:
            print "P3D was converted by ODOL Explorer, Enabled OdolEx compatibility mode."
            self.OdolEx = True
        elif vers != 0x00000101:
            raise P3DParseError("Only MLOD version 0x00000101 supported, found: 0x%x" % vers)
            
        lodcount = struct.unpack("i", file.read(4))[0]
        for n in range(lodcount):
            lodTag = struct.unpack("4s", file.read(4))[0]
            if lodTag == 'SP3X':
                self.readSP3X(file)
            elif lodTag == 'P3DM':
                self.readP3DM(file)
            else:
                raise P3DParseError("fourcc for LODs needs to be 'SP3X' or 'P3DM', not '%s'" % magic)
            
        if close:
            file.close()
            
            
    def readSP3X(self, file):
        if struct.unpack("i", file.read(4))[0] != 0x0000001c:
            raise P3DParseError("Wrong SP3X major version number %d" % vers)
        
        if struct.unpack("i", file.read(4))[0] != 0x00000099:
            raise P3DParseError("Wrong SP3X minor version number %d" % vers)
            
        newlod = self.createLOD()
        newlod.setSP3X()
        
        vertexCount = struct.unpack("i", file.read(4))[0]
        normalCount = struct.unpack("i", file.read(4))[0]
        polyCount = struct.unpack("i", file.read(4))[0]
        newlod.flags = struct.unpack("i", file.read(4))[0]
        
        for v in xrange(vertexCount):
            fvertex = struct.unpack("3fI", file.read(16))
            newlod.vertices.append( Vert(fvertex[:3], fvertex[3]) )
            
        for v in xrange(normalCount):
            fnormal = struct.unpack("3f", file.read(12))
            newlod.normals.append( Normal(fnormal) )
            
        fmt = "32siiiffiiffiiffiiffI"
        fmts = struct.calcsize(fmt)
        
        for v in range(polyCount):
            fpoly = struct.unpack(fmt, file.read(fmts))
            
            # everything after the terminating 0 needs to be
            # stripped from the string (there's not guarantee
            # it's all filled with 0s).
            path = fpoly[0]
            path = path[:path.find('\x00')]
            
            numvert = fpoly[1]
            if numvert == 3:
                vIndex = [ fpoly[2], fpoly[6], fpoly[10] ]
                nIndex = [ fpoly[3], fpoly[7], fpoly[11] ]
                uv = [ (fpoly[4], fpoly[5]), (fpoly[8],fpoly[9]), (fpoly[12],fpoly[13]) ]
            else:
                vIndex = [ fpoly[2], fpoly[6], fpoly[10], fpoly[14] ]
                nIndex = [ fpoly[3], fpoly[7], fpoly[11], fpoly[15] ]
                uv = [ (fpoly[4], fpoly[5]), (fpoly[8],fpoly[9]), (fpoly[12],fpoly[13]), (fpoly[16],fpoly[17]) ]
                
            flags = fpoly[18]
            newface = Face( vIndex, nIndex, path.lower(), uv, flags )
            newlod.polygons.append(newface)
            
        # now the taggs...
        if struct.unpack("4s", file.read(4))[0] != "TAGG":
            raise P3DParseError("Missing 'TAGG' fourcc after polygon data, found: '%s'" % magic)
        
        while True:
            taggname = struct.unpack("64s", file.read(64))[0]
            taggname = taggname[:taggname.find('\x00')]
            taggsize = struct.unpack("i", file.read(4))[0]
            
            if( taggname == "#EndOfFile#" ):
                break
            elif( taggname == "#SharpEdges#" ):
                for i in xrange(taggsize / 8):
                    newlod.sharpEdges.append(struct.unpack("ii", file.read(8)))
            elif( taggname == "#Property#" ):
                for v in xrange(taggsize / 128):
                    key, value = struct.unpack("64s64s", file.read(128))
                    newlod.properties[key[:key.find('\x00')]] = value[:value.find('\x00')]
                    
            elif( taggname == "#MaterialIndex#" ):
                file.seek(taggsize,1)
            elif( taggname == "#Animation#" ):
                file.seek(taggsize,1)
            elif( taggname == "#Mass#" ):
                if len(newlod.vertices) != taggsize/4 and not self.OdolEx:
                    raise P3DParseError("Taggsize expected to be %d, not %d" % (len(newlod.vertices), taggsize))
                for v in xrange(len(newlod.vertices)):
                    newlod.mass.append( struct.unpack("f", file.read(4))[0] )
            elif( taggname == "#Selected#" ):
                file.seek(taggsize,1)
            elif( taggname == "#Hide#" ):
                file.seek(taggsize,1)
            elif( taggname == "#Lock#" ):
                file.seek(taggsize,1)
            elif( taggname == "#UVSet#" ):
                file.seek(taggsize,1)
            elif( taggname[0] == '#' and taggname[-1] == '#' ):
                print "Skipped unknown tagg: %s" % taggname
                file.seek(taggsize,1)
            else:
                if (len(newlod.vertices) + len(newlod.polygons)) != taggsize and not self.OdolEx:
                    raise P3DParseError("Taggsize expected to be %d, not %d" % (len(newlod.vertices) + len(newlod.polygons), taggsize))
                vertices = [struct.unpack("B", file.read(1))[0] for X in xrange(len(newlod.vertices))]
                faces = [struct.unpack("B", file.read(1))[0] for X in xrange(len(newlod.polygons))]
                newlod.selections[taggname.lower()] = SP3X_Selection(vertices, faces)
                
        resString = file.read(4)
        fVal = struct.unpack('f', resString)[0]
        iVal = struct.unpack('i', resString)[0]
        
        if fVal < 1.0e3:
            newlod.resolution = fVal
        else:
            newlod.resolution = iVal
            
            
    def readP3DM(self, file):
        if struct.unpack("i", file.read(4))[0] != 0x0000001c:
            raise P3DParseError("Wrong P3DM major version number %d" % vers)
        
        if struct.unpack("i", file.read(4))[0] != 0x00000100: # 255
            raise P3DParseError("Wrong P3DM minor version number %d" % vers)
            
        newlod = self.createLOD()
        newlod.setP3DM()
        
        vertexCount = struct.unpack("i", file.read(4))[0]
        normalCount = struct.unpack("i", file.read(4))[0]
        polyCount = struct.unpack("i", file.read(4))[0]
        flags = struct.unpack("i", file.read(4))[0]
        
        for v in xrange(vertexCount):
            fvertex = struct.unpack("3fI", file.read(16))
            newlod.vertices.append( Vert(fvertex[:3], fvertex[3]) )
            
        for v in xrange(normalCount):
            fnormal = struct.unpack("3f", file.read(12))
            newlod.normals.append( Normal(fnormal) )
            
        fmt = "iiiffiiffiiffiiffI"
        fmts = struct.calcsize(fmt)
        
        for v in range(polyCount):
            fpoly = struct.unpack(fmt, file.read(fmts))
            
            numvert = fpoly[0]
            vIndex = [ fpoly[1], fpoly[5], fpoly[9] ]
            nIndex = [ fpoly[2], fpoly[6], fpoly[10] ]
            uv = [ (fpoly[3], fpoly[4]), (fpoly[7],fpoly[8]), (fpoly[11],fpoly[12]) ]
                
            if numvert == 4:
                vIndex.append(fpoly[13])
                nIndex.append(fpoly[14])
                uv.append((fpoly[15],fpoly[16]))
                
            flags = fpoly[17]
            
            path = getZString(file)
            material = getZString(file)
            
            newface = Face( vIndex, nIndex, path.lower(), uv, flags )
            newface.material = material
            
            newlod.polygons.append(newface)
            
        # now the taggs...
        if struct.unpack("4s", file.read(4))[0] != "TAGG":
            raise P3DParseError("Missing 'TAGG' fourcc after polygon data, found: '%s'" % magic)
            
        while True:
            taggbyte = struct.unpack("B", file.read(1))[0] # what is this supposed to be?
            taggname = getZString(file)
            taggsize = struct.unpack("i", file.read(4))[0]
            
            if( taggname == "#EndOfFile#" ):
                break
                
            elif( taggname == "#SharpEdges#" ):
                for i in xrange(taggsize / 8):
                    newlod.sharpEdges.append(struct.unpack("ii", file.read(8)))
                
            elif( taggname == "#Property#" ):
                for v in xrange(taggsize / 128):
                    key, value = struct.unpack("64s64s", file.read(128))
                    newlod.properties[key[:key.find('\x00')]] = value[:value.find('\x00')]
                    
            elif( taggname == "#MaterialIndex#" ):
                file.seek(taggsize, 1)
                
            elif( taggname == "#Animation#" ):
                file.seek(taggsize,1)
                
            elif( taggname == "#Mass#" ):
                for v in xrange(taggsize / 4):
                    newlod.mass.append( struct.unpack("f", file.read(4))[0] )
                
            elif taggname in ['#Selected#', '#Hide#', '#Lock#']:
                file.seek(taggsize, 1)
                
            elif( taggname == "#UVSet#" ):
                # read in the UVset, correct it later!
                newlod.uvset = file.read(taggsize)
                
            elif( taggname[0] == '#' and taggname[-1] == '#' ):
                print "Skipped unknown tagg: %s" % taggname
                file.seek(taggsize, 1)
                
            else:
                #newselection = P3DM_Selection()
                if (len(newlod.vertices) + len(newlod.polygons)) != taggsize:
                    raise P3DParseError("Taggsize expected to be %d, not %d" % (len(newlod.vertices), taggsize))
                vertices = [struct.unpack("B", file.read(1))[0] for X in xrange(len(newlod.vertices))]
                faces = [struct.unpack("B", file.read(1))[0] for X in xrange(len(newlod.polygons))]
                newlod.selections[taggname.lower()] = P3DM_Selection(vertices, faces)
                
        newlod.resolution = struct.unpack("f", file.read(4))[0]
            
        
