###########################################################################
# cgkit - Python Computer Graphics Kit
# Copyright (C) 2004 Matthias Baas (baas@ira.uka.de)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# cgkit homepage: http://cgkit.sourceforge.net
###########################################################################
# $Id: lwob.py,v 1.1 2006/03/12 22:41:42 mbaas Exp $

import struct
from cgtypes import vec3

class LWOBError(Exception):
    pass

# Texture
class Texture:
    """Texture description.

    A value remains None if it wasn't set in the LWOB file.
    """
    def __init__(self):
        self.type = None        # str
        self.flags = None       # int
        self.size = None        # vec3
        self.center = None      # vec3
        self.falloff = None     # vec3
        self.velocity = None    # vec3
        self.color = None       # 3-tuple
        self.value = None       # int
        self.bumpamplitude = None # float
        self.floatparams = 10*[None]  # list of floats
        self.intparams = 10*[None]    # list of ints
        self.imagename = None   # str
        self.alphaname = None   # str
        self.widthwrap = None   # int
        self.heightwrap = None  # int
        self.antialiasingstrength = None # float
        self.opacity = None     # float
        self.shader = []        # list of str
        self.shaderdata = []    # list of str
        self.seq_offset = None  # int
        self.seq_flags = None   # int
        self.seq_looplength = None # int
        self.flyer_begin = None # int
        self.flyer_end = None   # int
        self.cycle_speed = None # int
        self.cycle_low = None   # int
        self.cycle_high = None  # int

# Surface
class Surface:
    """Surface description.

    A value remains None if it wasn't set in the LWOB file.    
    """
    
    def __init__(self, name):
        self.name = name
        
        self.color = None          # 3-tuple
        self.flags = None          # int
        self.luminosity = None     # float
        self.diffuse = None        # float
        self.specular = None       # float
        self.reflection = None     # float
        self.transparency = None   # float
        self.glossiness = None     # int
        self.reflectionmode = None # int
        self.refmap = None         # str
        self.refmap_seamangle = None # float
        self.refractiveindex = None  # float
        self.edgetransp = None     # float
        self.maxsmoothangle = None # float

        self.tex_color = []        # list of Texture objects
        self.tex_diffuse = []
        self.tex_specular = []
        self.tex_reflection = []
        self.tex_transparency = []
        self.tex_luminosity = []
        self.tex_bump = []
        

# LWOBReader
class LWOBReader:
    """Lightwave Object reader.

    This class reads Lightwave Object (*.lwo) files and calls handler
    methods for each chunk found. The following handler methods can be
    implemented in derived classes:

    - handlePNTS(points):  points is a list of vec3 objects containing
                           all vertices.
    - handleSRFS(names):   names is a list of strings containing the names
                           of all surface definitions.
    - handlePOLS(polys):   polys is a list of polygons. Each polygon is a
                           tuple (verts, surface) where verts is a list of
                           vertex indices and surface the index of the surface
                           that is used by this polygon.
    - handleCRVS(curves):  curves is a list of curves. Each curve is a
                           tuple (verts, surface, flags).
    - handlePCHS(patches): patches is a list of patches. Each patch is a
                           tuple (verts, surface).
    - handleSURF(surface): surface is a Surface object that contains the
                           surface attributes.
    """

    def __init__(self):
        """Constructor.
        """
        # The file handle
        self._file = None
        # The number of bytes that are left in the file.
        # After the FORM has been read, it is initialized with the
        # FORM length and is used to determine whether there are still
        # more chunks left or not.
        self._bytes_left = 12

        # Current Surface object when reading a SURF chunk
        self._current_surface = None
        # Current Texture object
        self._current_tex = None

    def read(self, file):
        """Read the lwo file.

        file is a file-like object that provides the read() and seek()
        methods.
        """
        self._file = file
        self._bytes_left = 12
        
        # Read the FORM header...
        tag, length = self.readChunkHeader()
        self.onChunk(tag, length, 0)
        self._bytes_left = length
        if tag!="FORM":
            raise LWOBError("Not a Lightwave object file")

        # Read the LWOB tag...
        lwob = self.readNBytes(4)
        if lwob!="LWOB":
            raise LWOBError("Not a Lightwave object file")

        # Read the contained chunks and call the 'reader' method if available.
        # If no reader is defined for a chunk this chunk is skipped.
        while not self._eofReached():
            tag,length = self.readChunkHeader()
            self.onChunk(tag, length, 1)
#            print tag, length
            handlername = "read%s"%tag
            handler = getattr(self, handlername, None)
            if handler==None:
                self.skipChunk(length)
            else:
                handler(length)


    # Handler methods. These methods can be overridden in derived classes.

    def onChunk(self, tag, length, level): pass

    def handlePNTS(self, points): pass
    def handleSRFS(self, names): pass
    def handlePOLS(self, polys): pass
    def handleCRVS(self, curves): pass
    def handlePCHS(self, patches): pass
    def handleSURF(self, surface): pass


    ### readXXXX() reader methods (XXXX is the chunk tag)
    ### A reader takes the chunk length as argument and has to read
    ### its data from the file and call an appropriate handler method
    ### (handleXXXX(...))

    def readPNTS(self, length):
        """Read a PNTS chunk and call the PNTS handler.
        """
        if length%12!=0:
            raise LWOBError, "Invalid PNTS chunk size (%d)"%length
        
        numpnts = int(length/12)
        # Read the chunk data...
        data = self.readNBytes(length)
        # Unpack the coordinates...
        coords = struct.unpack(">%s"%(numpnts*"fff"), data)
        # Initialize vec3s...
        pnts = []
        for i in range(numpnts):
            pnts.append(vec3(coords[i*3:(i+1)*3]))
            
        self.handlePNTS(pnts)

    def readSRFS(self, length):
        """Read a SRFS chunk and call the SRFS handler.
        """
        data = self.readNBytes(length)
        names = []
        while len(data)>0:
            n = data.find("\000")
            if n==-1:
                n = len(data)
            names.append(data[:n])
            if n%2==0:
                n+=1
            data = data[n+1:]

        self.handleSRFS(names)

    def readPOLS(self, length):
        """Read a POLS chunk and call the POLS handler.
        """
        data = self.readNBytes(length)
        idx = 0
        unpack = struct.unpack
        polys = []
        # The number of detail polygons that follow
        # (detail polygons are ignored)
        details_left = 0
        while idx<len(data):
            numverts = unpack(">H", data[idx:idx+2])[0]
            idx += 2
            verts = unpack(">%s"%(numverts*"H"), data[idx:idx+numverts*2])
            idx += numverts*2
            surf = unpack(">H", data[idx:idx+2])[0]
            idx += 2
            if details_left==0:
                polys.append((verts, surf))
            else:
                details_left -= 1

            # Does the polygon have detail polygons?
            if surf<0:
                details_left = unpack(">H", data[idx:idx+2])[0]
                idx += 2

        self.handlePOLS(polys)

    def readCRVS(self, length):
        """Read a CRVS chunk and call the CRVS handler.
        """
        data = self.readNBytes(length)
        idx = 0
        unpack = struct.unpack
        curves = []
        while idx<len(data):
            numverts = unpack(">H", data[idx:idx+2])[0]
            idx += 2
            verts = unpack(">%s"%(numverts*"H"), data[idx:idx+numverts*2])
            idx += numverts*2
            surf,flags = unpack(">HH", data[idx:idx+4])
            idx += 4
            curves.append((verts, surf, flags))

        self.handleCRVS(curves)
        
    def readPCHS(self, length):
        """Read a PCHS chunk and call the PCHS handler.
        """
        data = self.readNBytes(length)
        idx = 0
        unpack = struct.unpack
        patches = []
        while idx<len(data):
            numverts = unpack(">H", data[idx:idx+2])[0]
            idx += 2
            verts = unpack(">%s"%(numverts*"H"), data[idx:idx+numverts*2])
            idx += numverts*2
            surf = unpack(">H", data[idx:idx+2])[0]
            idx += 2
            patched.append((verts, surf))

        self.handlePCHS(patches)

    def readSURF(self, length):
        """Read a SURF chunk and call the SURF handler.
        """
        # Read the surface name byte by byte...
        name = ""
        while 1:
            c = self.readNBytes(1)
            length -= 1
            if ord(c)==0:
                if len(name)%2==0:
                    # Skip the pad byte
                    self.readNBytes(1)
                    length -= 1
                break
            name += c

        self._current_surface = Surface(name)
        self._current_tex = None
        
#        print "SURFACE",name
        while length>0:
            tag,sublength = self.readSubChunkHeader()
            self.onChunk(tag, sublength, 2)
            length -= 6+sublength
#            print " ",tag,sublength
            handlername = "readSURF_%s"%tag
            handler = getattr(self, handlername, None)
            if handler==None:
                self.skipChunk(sublength)
            else:
                handler(sublength)

        self.handleSURF(self._current_surface)
        self._current_tex = None
        self._current_surface = None

    def readSURF_COLR(self, length):
        """COLR chunk."""
        if length!=4:
            raise LWOBError, "Invalid COLR size (%d instead of 4)"%length
        data = self.readNBytes(4)
        color = struct.unpack(">BBB", data[:3])
        self._current_surface.color = color

    def readSURF_FLAG(self, length):
        """FLAG chunk."""
        if length!=2:
            raise LWOBError, "Invalid FLAG size (%d instead of 2)"%length
        data = self.readNBytes(2)
        flags = struct.unpack(">H", data)[0]
        self._current_surface.flags = flags
        
    def readSURF_LUMI(self, length):
        """LUMI chunk."""
        if length!=2:
            raise LWOBError, "Invalid LUMI size (%d instead of 2)"%length
        data = self.readNBytes(2)
        lumi = struct.unpack(">H", data)[0]
        if self._current_surface.luminosity==None:
            self._current_surface.luminosity = lumi/256.0

    def readSURF_VLUM(self, length):
        """VLUM chunk."""
        if length!=4:
            raise LWOBError, "Invalid VLUM size (%d instead of 4)"%length
        data = self.readNBytes(4)
        lum = struct.unpack(">f", data)[0]
        self._current_surface.luminosity = lum

    def readSURF_DIFF(self, length):
        """DIFF chunk."""
        if length!=2:
            raise LWOBError, "Invalid DIFF size (%d instead of 2)"%length
        data = self.readNBytes(2)
        diff = struct.unpack(">H", data)[0]
        if self._current_surface.diffuse==None:
            self._current_surface.diffuse = diff/256.0

    def readSURF_VDIF(self, length):
        """VDIF chunk."""
        if length!=4:
            raise LWOBError, "Invalid VDIF size (%d instead of 4)"%length
        data = self.readNBytes(4)
        diff = struct.unpack(">f", data)[0]
        self._current_surface.diffuse = diff

    def readSURF_SPEC(self, length):
        """SPEC chunk."""
        if length!=2:
            raise LWOBError, "Invalid SPEC size (%d instead of 2)"%length
        data = self.readNBytes(2)
        spec = struct.unpack(">H", data)[0]
        if self._current_surface.specular==None:
            self._current_surface.specular = spec/256.0

    def readSURF_VSPC(self, length):
        """VSPC chunk."""
        if length!=4:
            raise LWOBError, "Invalid VSPC size (%d instead of 4)"%length
        data = self.readNBytes(4)
        spec = struct.unpack(">f", data)[0]
        self._current_surface.specular = spec

    def readSURF_REFL(self, length):
        """REFL chunk."""
        if length!=2:
            raise LWOBError, "Invalid REFL size (%d instead of 2)"%length
        data = self.readNBytes(2)
        refl = struct.unpack(">H", data)[0]
        if self._current_surface.reflection==None:
            self._current_surface.reflection = refl/256.0

    def readSURF_VRFL(self, length):
        """VRFL chunk."""
        if length!=4:
            raise LWOBError, "Invalid VRFL size (%d instead of 4)"%length
        data = self.readNBytes(4)
        refl = struct.unpack(">f", data)[0]
        self._current_surface.reflection = refl

    def readSURF_TRAN(self, length):
        """TRAN chunk."""
        if length!=2:
            raise LWOBError, "Invalid TRAN size (%d instead of 2)"%length
        data = self.readNBytes(2)
        tran = struct.unpack(">H", data)[0]
        if self._current_surface.transparency==None:
            self._current_surface.transparency = tran/256.0

    def readSURF_VTRN(self, length):
        """VTRN chunk."""
        if length!=4:
            raise LWOBError, "Invalid VTRN size (%d instead of 4)"%length
        data = self.readNBytes(4)
        tran = struct.unpack(">f", data)[0]
        self._current_surface.transparency = tran

    def readSURF_GLOS(self, length):
        """GLOS chunk."""
        if length!=2:
            raise LWOBError, "Invalid GLOS size (%d instead of 2)"%length
        data = self.readNBytes(2)
        glos = struct.unpack(">H", data)[0]
        self._current_surface.glossiness = glos

    def readSURF_RFLT(self, length):
        """RFLT chunk."""
        if length!=2:
            raise LWOBError, "Invalid RFLT size (%d instead of 2)"%length
        data = self.readNBytes(2)
        mode = struct.unpack(">H", data)[0]
        self._current_surface.reflectionmode = mode

    def readSURF_RIMG(self, length):
        """RIMG chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]
        self._current_surface.refmap = data

    def readSURF_RSAN(self, length):
        """RSAN chunk."""
        if length!=4:
            raise LWOBError, "Invalid RSAN size (%d instead of 4)"%length
        data = self.readNBytes(4)
        deg = struct.unpack(">f", data)[0]
        self._current_surface.refmap_seamangle = deg

    def readSURF_RIND(self, length):
        """RIND chunk."""
        if length!=4:
            raise LWOBError, "Invalid RIND size (%d instead of 4)"%length
        data = self.readNBytes(4)
        ior = struct.unpack(">f", data)[0]
        self._current_surface.refractiveindex = ior

    def readSURF_EDGE(self, length):
        """EDGE chunk."""
        if length!=4:
            raise LWOBError, "Invalid EDGE size (%d instead of 4)"%length
        data = self.readNBytes(4)
        edge = struct.unpack(">f", data)[0]
        self._current_surface.edgetransp = edge

    def readSURF_SMAN(self, length):
        """SMAN chunk."""
        if length!=4:
            raise LWOBError, "Invalid SMAN size (%d instead of 4)"%length
        data = self.readNBytes(4)
        sman = struct.unpack(">f", data)[0]
        self._current_surface.maxsmoothangle = sman

    def readSURF_CTEX(self, length):
        """CTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_color.append(tex)

    def readSURF_DTEX(self, length):
        """DTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_diffuse.append(tex)

    def readSURF_STEX(self, length):
        """STEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_specular.append(tex)

    def readSURF_RTEX(self, length):
        """RTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_reflection.append(tex)

    def readSURF_TTEX(self, length):
        """TTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_transparency.append(tex)

    def readSURF_LTEX(self, length):
        """LTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_luminosity.append(tex)

    def readSURF_BTEX(self, length):
        """BTEX chunk."""
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        tex = Texture()
        tex.type = data
        self._current_tex = tex
        self._current_surface.tex_bump.append(tex)

    def readSURF_TFLG(self, length):
        """TFLG chunk."""
        if length!=2:
            raise LWOBError, "Invalid TFLG size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFLG chunk"
        data = self.readNBytes(2)
        flags = struct.unpack(">H", data)[0]
        self._current_tex.flags = flags

    def readSURF_TSIZ(self, length):
        """TSIZ chunk."""
        if length!=12:
            raise LWOBError, "Invalid TSIZ size (%d instead of 12)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TSIZ chunk"
        data = self.readNBytes(12)
        v = vec3(struct.unpack(">fff", data))
        self._current_tex.size = v

    def readSURF_TCTR(self, length):
        """TCTR chunk."""
        if length!=12:
            raise LWOBError, "Invalid TCTR size (%d instead of 12)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TCTR chunk"
        data = self.readNBytes(12)
        v = vec3(struct.unpack(">fff", data))
        self._current_tex.center = v

    def readSURF_TFAL(self, length):
        """TFAL chunk."""
        if length!=12:
            raise LWOBError, "Invalid TFAL size (%d instead of 12)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFAL chunk"
        data = self.readNBytes(12)
        v = vec3(struct.unpack(">fff", data))
        self._current_tex.falloff = v

    def readSURF_TVEL(self, length):
        """TVEL chunk."""
        if length!=12:
            raise LWOBError, "Invalid TVEL size (%d instead of 12)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TVEL chunk"
        data = self.readNBytes(12)
        v = vec3(struct.unpack(">fff", data))
        self._current_tex.velocity = v

    def readSURF_TCLR(self, length):
        """TCLR chunk."""
        if length!=4:
            raise LWOBError, "Invalid TCLR size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TCLR chunk"
        data = self.readNBytes(4)
        col = struct.unpack(">BBB", data[:3])
        self._current_tex.color = v

    def readSURF_TVAL(self, length):
        """TVAL chunk."""
        if length!=2:
            raise LWOBError, "Invalid TVAL size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TVAL chunk"
        data = self.readNBytes(2)
        val = struct.unpack(">H", data)[0]
        self._current_tex.value = val

    def readSURF_TAMP(self, length):
        """TAMP chunk."""
        if length!=4:
            raise LWOBError, "Invalid TAMP size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TAMP chunk"
        data = self.readNBytes(4)
        amp = struct.unpack(">f", data)[0]
        self._current_tex.bumpamplitude = amp

    def readSURF_TFP0(self, length):
        """TFP0 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP0 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP0 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[0] = f

    def readSURF_TFP1(self, length):
        """TFP1 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP1 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP1 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[1] = f

    def readSURF_TFP2(self, length):
        """TFP2 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP2 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP2 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[2] = f

    def readSURF_TFP3(self, length):
        """TFP3 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP3 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP3 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[3] = f

    def readSURF_TFP4(self, length):
        """TFP4 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP4 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP4 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[4] = f

    def readSURF_TFP5(self, length):
        """TFP5 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP5 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP5 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[5] = f

    def readSURF_TFP6(self, length):
        """TFP6 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP6 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP6 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[6] = f

    def readSURF_TFP7(self, length):
        """TFP7 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP7 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP7 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[7] = f

    def readSURF_TFP8(self, length):
        """TFP8 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP8 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP8 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[8] = f

    def readSURF_TFP9(self, length):
        """TFP9 chunk."""
        if length!=4:
            raise LWOBError, "Invalid TFP9 size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TFP9 chunk"
        data = self.readNBytes(4)
        f = struct.unpack(">f", data)[0]
        self._current_tex.floatparams[9] = f

    def readSURF_TIP0(self, length):
        """TIP0 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP0 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP0 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[0] = i

    def readSURF_TIP1(self, length):
        """TIP1 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP1 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP1 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[1] = i

    def readSURF_TIP2(self, length):
        """TIP2 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP2 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP2 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[2] = i

    def readSURF_TIP3(self, length):
        """TIP3 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP3 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP3 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[3] = i

    def readSURF_TIP4(self, length):
        """TIP4 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP4 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP4 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[4] = i

    def readSURF_TIP5(self, length):
        """TIP5 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP5 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP5 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[5] = i

    def readSURF_TIP6(self, length):
        """TIP6 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP6 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP6 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[6] = i

    def readSURF_TIP7(self, length):
        """TIP7 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP7 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP7 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[7] = i

    def readSURF_TIP8(self, length):
        """TIP8 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP8 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP8 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[8] = i

    def readSURF_TIP9(self, length):
        """TIP9 chunk."""
        if length!=2:
            raise LWOBError, "Invalid TIP9 size (%d instead of 2)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIP9 chunk"
        data = self.readNBytes(2)
        i = struct.unpack(">h", data)[0]
        self._current_tex.intparams[9] = i

    def readSURF_TIMG(self, length):
        """TIMG chunk."""
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TIMG chunk"
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        self._current_tex.imagename = data

    def readSURF_TALP(self, length):
        """TALP chunk."""
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TALP chunk"
        data = self.readNBytes(length)
        n = data.find("\000")
        if n!=-1:
            data = data[:n]

        self._current_tex.alphaname = data

    def readSURF_TWRP(self, length):
        """TWRP chunk."""
        if length!=4:
            raise LWOBError, "Invalid TWRP size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TWRP chunk"
        data = self.readNBytes(4)
        w,h = struct.unpack(">HH", data)
        self._current_tex.widthwrap = w
        self._current_tex.heightwrap = h

    def readSURF_TAAS(self, length):
        """TAAS chunk."""
        if length!=4:
            raise LWOBError, "Invalid TAAS size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TAAS chunk"
        data = self.readNBytes(4)
        aa = struct.unpack(">f", data)[0]
        self._current_tex.antialiasingstrength = aa

    def readSURF_TOPC(self, length):
        """TOPC chunk."""
        if length!=4:
            raise LWOBError, "Invalid TOPC size (%d instead of 4)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the TOPC chunk"
        data = self.readNBytes(4)
        op = struct.unpack(">f", data)[0]
        self._current_tex.opacity = op

    def readSURF_SHDR(self, length):
        """SHDR chunk."""
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the SHDR chunk"
        name = self.readNBytes(length)
        n = name.find("\000")
        if n!=-1:
            name = name[:n]

        self._current_tex.shader.append(name)

    def readSURF_SDAT(self, length):
        """SDAT chunk."""
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the SDAT chunk"
        data = self.readNBytes(length)
        self._current_tex.shaderdata.append(data)

    def readSURF_IMSQ(self, length):
        """IMSQ chunk."""
        if length!=6:
            raise LWOBError, "Invalid IMSQ size (%d instead of 6)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the IMSQ chunk"
        data = self.readNBytes(6)
        offset,flags,looplen = struct.unpack(">HHH", data)
        self._current_tex.seq_offset = offset
        self._current_tex.seq_flags = flags
        self._current_tex.seq_looplength = looplen

    def readSURF_FLYR(self, length):
        """FLYR chunk."""
        if length!=8:
            raise LWOBError, "Invalid FLYR size (%d instead of 8)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the FLYR chunk"
        data = self.readNBytes(8)
        b,e = struct.unpack(">II", data)
        self._current_tex.flyer_begin = b
        self._current_tex.flyer_end = e

    def readSURF_IMCC(self, length):
        """IMCC chunk."""
        if length!=6:
            raise LWOBError, "Invalid IMCC size (%d instead of 6)"%length
        if self._current_tex==None:
            raise LWOBError, "Invalid position of the IMCC chunk"
        data = self.readNBytes(6)
        speed,low,high = struct.unpack(">HHH", data)
        self._current_tex.cycle_speed = speed
        self._current_tex.cycle_low = low
        self._current_tex.cycle_high = high


    #####

    def skipChunk(self, length):
        """Skip a chunk/sub chunk in the file (without reading it).

        Sets the file's position to the begin of the next chunk.
        length is the chunk length as it is stored in the file.
        The method takes care of adding the pad byte if length is odd.
        """
        if length%2==1:
            length += 1
        self._file.seek(length, 1)
        self._bytes_left -= length

    def readChunkHeader(self):
        """Read the head of the next chunk.

        Returns a tuple (tag, length).
        """
        s = self.readNBytes(8)
        tag = s[:4]
        length = struct.unpack(">I", s[4:])[0]
        return tag, length

    def readSubChunkHeader(self):
        """Read the head of the next sub chunk.

        Returns a tuple (tag, length).
        """
        s = self.readNBytes(6)
        tag = s[:4]
        length = struct.unpack(">H", s[4:])[0]
        return tag, length

    def readNBytes(self, n):
        """Read n bytes.

        Throws an exception if less than n bytes were read.
        """
        s = self._file.read(n)
        self._bytes_left -= len(s)
        if len(s)!=n:
            raise LWOBError, "premature end of file"
        return s

    def _eofReached(self):
        """Return True when the end of the file has been reached.
        """
        return self._bytes_left<=1
            
