text: # vim: tabstop=8 noexpandtab
  
  from collections import namedtuple
  from struct import pack, unpack
  from enum import Enum
  
  import math
  from mathutils import Euler, Matrix, Quaternion, Vector
  
  def bit(n):
          return 1 << n
  
  class Box:
          def __init__(self, min, max):
                  self.min = min
                  self.max = max
  
          def __repr__(self):
                  return "({}, {})".format(self.min, self.max)
  
  class Node:
          def __init__(self, name, parent=-1):
                  self.name = name
                  self.parent = parent
  
                  # Unused
                  self.firstObject = -1
                  self.firstChild = -1
                  self.nextSibling = -1
  
          def write(self, stream):
                  stream.write32(
                          self.name, self.parent,
                          self.firstObject, self.firstChild, self.nextSibling)
  
          @classmethod
          def read(cls, stream):
                  obj = cls(stream.read32(), stream.read32())
                  obj.firstObject = stream.read32()
                  obj.firstChild = stream.read32()
                  obj.nextSibling = stream.read32()
                  return obj
  
  class Object:
          def __init__(self, name, numMeshes, firstMesh, node):
                  self.name = name
                  self.numMeshes = numMeshes
                  self.firstMesh = firstMesh
                  self.node = node
  
                  # Unused
                  self.nextSibling = -1
                  self.firstDecal = -1
  
          def write(self, stream):
                  stream.write32(
                          self.name, self.numMeshes, self.firstMesh,
                          self.node, self.nextSibling, self.firstDecal)
  
          @classmethod
          def read(cls, stream):
                  obj = cls(stream.read32(), stream.read32(), stream.read32(), stream.read32())
                  obj.nextSibling = stream.read32()
                  obj.firstDecal = stream.read32()
                  return obj
  
  class IflMaterial:
          def __init__(self, name, slot):
                  self.name = name
                  self.slot = slot
  
                  # Unused
                  self.firstFrame = -1
                  self.time = -1
                  self.numFrames = -1
  
          def write(self, stream):
                  stream.write32(
                          self.name, self.slot, self.firstFrame,
                          self.time, self.numFrames)
  
          @classmethod
          def read(cls, stream):
                  instance = cls(
                          stream.read32(), stream.read32())
                  instance.firstFrame = stream.read32()
                  instance.time = stream.read32()
                  instance.numFrames = stream.read32()
                  return instance
  
  class Subshape:
          def __init__(self, firstNode, firstObject, firstDecal, numNodes, numObjects, numDecals):
                  self.firstNode = firstNode
                  self.firstObject = firstObject
                  self.firstDecal = firstDecal
                  self.numNodes = numNodes
                  self.numObjects = numObjects
                  self.numDecals = numDecals
  
  class ObjectState:
          def __init__(self, vis, frame, matFrame):
                  self.vis = vis
                  self.frame = frame
                  self.matFrame = matFrame
  
          def write(self, stream):
                  stream.write_float(self.vis)
                  stream.write32(self.frame, self.matFrame)
  
          @classmethod
          def read(cls, stream):
                  return cls(stream.read_float(), stream.read32(), stream.read32())
  
  class Trigger:
          StateOn = bit(31)
          InvertOnReverse = bit(30)
  
          def __init__(self, state, pos):
                  self.state = state
                  self.pos = pos
  
          def write(self, stream):
                  stream.write32(self.state)
                  stream.write_float(self.pos)
  
          @classmethod
          def read(cls, stream):
                  return cls(stream.read32(), stream.read_float())
  
  class DetailLevel:
          def __init__(self, name, subshape, objectDetail, size, avgError=-1.0, maxError=-1.0, polyCount=0):
                  self.name = name
                  self.subshape = subshape
                  self.objectDetail = objectDetail
                  self.size = size
  
                  # Unused
                  self.avgError = -1.0
                  self.maxError = -1.0
                  self.polyCount = 0
  
          def write(self, stream):
                  stream.write32(self.name, self.subshape, self.objectDetail)
                  stream.write_float(self.size, self.avgError, self.maxError)
                  stream.write32(self.polyCount)
  
          @classmethod
          def read(cls, stream):
                  obj = cls(stream.read32(), stream.read32(), stream.read32(), stream.read_float())
                  obj.avgError = stream.read_float()
                  obj.maxError = stream.read_float()
                  obj.polyCount = stream.read32()
                  return obj
  
  class Primitive:
          Triangles = 0x00000000
          Strip = 0x40000000
          Fan = 0x80000000
          TypeMask = 0xC0000000
          Indexed = 0x20000000
          NoMaterial = 0x10000000
          MaterialMask = 0x0FFFFFFF
  
          def __init__(self, firstElement, numElements, type):
                  self.firstElement = firstElement
                  self.numElements = numElements
                  self.type = type
  
          def write(self, stream):
                  stream.write16(self.firstElement, self.numElements)
                  stream.write32(self.type)
  
          @classmethod
          def read(cls, stream):
                  return cls(stream.read16(), stream.read16(), stream.read32())
  
  class Mesh:
          StandardType = 0
          SkinType = 1
          DecalType = 2
          SortedType = 3
          NullType = 4
          TypeMask = 7
  
          TypeName = ["Standard", "Skin", "Decal", "Sorted", "Null"]
  
          Billboard = bit(31)
          HasDetailTexture = bit(30)
          BillboardZAxis = bit(29)
          UseEncodedNormals = bit(28)
  
          def __init__(self, mtype):
                  self.bounds = Box(Vector(), Vector())
                  self.center = Vector()
                  self.radius = 0
                  self.numFrames = 1
                  self.numMatFrames = 1
                  self.vertsPerFrame = 1
                  self.parent = -1
                  self.type = mtype
                  self.verts = []
                  self.tverts = []
                  self.normals = []
                  self.enormals = []
                  self.primitives = []
                  self.indices = []
                  self.mindices = []
  
                  self.bones = []
                  self.influences = []
  
          def get_type(self):
                  return self.type & Mesh.TypeMask
  
          def get_flags(self, flag=0xFFFFFFFF):
                  return self.type & flag
  
          def set_flags(self, flag):
                  self.type |= flag
  
          def transformed_verts(self, mat):
                  return map(lambda vert: mat * vert, self.verts)
  
          def calculate_bounds_mat(self, mat):
                  box = Box(
                          Vector(( 10e30,  10e30,  10e30)),
                          Vector((-10e30, -10e30, -10e30)))
  
                  for vert in self.transformed_verts(mat):
                          box.min.x = min(box.min.x, vert.x)
                          box.min.y = min(box.min.y, vert.y)
                          box.min.z = min(box.min.z, vert.z)
                          box.max.x = max(box.max.x, vert.x)
                          box.max.y = max(box.max.y, vert.y)
                          box.max.z = max(box.max.z, vert.z)
  
                  return box
  
          def calculate_radius_mat(self, mat, center):
                  radius = 0.0
  
                  for vert in self.transformed_verts(mat):
                          radius = max(radius, (vert - center).length)
  
                  return radius
  
          def calculate_radius_tube_mat(self, mat, center):
                  radius = 0
  
                  for vert in self.transformed_verts(mat):
                          delta = vert - center
                          radius = max(radius, Vector((delta.x, delta.y)).length)
  
                  return radius
  
          def write(self, stream):
                  mtype = self.get_type()
                  stream.write32(self.type)
  
                  if mtype == Mesh.NullType:
                          return
  
                  stream.guard()
                  stream.write32(self.numFrames, self.numMatFrames, self.parent)
                  stream.write_box(self.bounds)
                  stream.write_vec3(self.center)
                  stream.write_float(self.radius)
  
                  # Geometry data
                  stream.write32(len(self.verts))
                  for vert in self.verts:
                          stream.write_vec3(vert)
                  stream.write32(len(self.tverts))
                  for tvert in self.tverts:
                          stream.write_vec2(tvert)
  
                  assert len(self.normals) == len(self.verts)
                  assert len(self.enormals) == len(self.verts)
                  for normal in self.normals:
                          stream.write_vec3(normal)
                  for enormal in self.enormals:
                          stream.write8(enormal)
  
                  # Primitives and other stuff
                  stream.write32(len(self.primitives))
                  for prim in self.primitives:
                          prim.write(stream)
  
                  #if stream.dtsVersion >= 25:
                  stream.write32(len(self.indices))
                  stream.write16(*self.indices)
                  stream.write32(len(self.mindices))
                  stream.write16(*self.mindices)
                  stream.write32(self.vertsPerFrame)
                  stream.write32(self.get_flags())
                  stream.guard()
  
                  if mtype == Mesh.SkinType:
                      stream.write32(len(self.verts))
                      for v in self.verts:
                          stream.write_vec3(v)
                      for v in self.normals:
                          stream.write_vec3(v)
                      stream.write8(*self.enormals)
  
                      stream.write32(len(self.bones))
                      for _, initial_transform in self.bones:
                          for f in initial_transform:
                              stream.write_float(f)
  
                      stream.write32(len(self.influences))
                      for vertex_index, _, _ in self.influences:
                          stream.write32(vertex_index)
                      for _, bone_index, _ in self.influences:
                          stream.write32(bone_index)
                      for _, _, weight in self.influences:
                          stream.write_float(weight)
  
                      stream.write32(len(self.bones))
                      for node_index, _ in self.bones:
                          stream.write32(node_index)
  
                      stream.guard()
                  elif mtype != Mesh.StandardType:
                          raise ValueError("cannot write {} mesh".format(mtype))
  
          def read_standard_mesh(self, stream):
                  stream.guard()
  
                  self.numFrames = stream.read32()
                  self.numMatFrames = stream.read32()
                  self.parent = stream.read32()
                  self.bounds = stream.read_box()
                  self.center = stream.read_vec3()
                  self.radius = stream.read_float()
  
                  # Geometry data
                  n_vert = stream.read32()
                  self.verts = [stream.read_vec3() for i in range(n_vert)]
                  n_tvert = stream.read32()
                  self.tverts = [stream.read_vec2() for i in range(n_tvert)]
                  self.normals = [stream.read_vec3() for i in range(n_vert)]
                  # TODO: don't read this when not relevant
                  self.enormals = [stream.read8() for i in range(n_vert)]
  
                  # Primitives and other stuff
                  self.primitives = [Primitive.read(stream) for i in range(stream.read32())]
                  self.indices = [stream.read16() for i in range(stream.read32())]
                  self.mindices = [stream.read16() for i in range(stream.read32())]
                  self.vertsPerFrame = stream.read32()
                  self.set_flags(stream.read32())
  
                  stream.guard()
  
          def read_skin_mesh(self, stream):
                  self.read_standard_mesh(stream)
  
                  sz = stream.read32()
                  _ = [stream.read_vec3() for i in range(sz)]
                  _ = [stream.read_vec3() for i in range(sz)]
                  _ = [stream.read8() for i in range(sz)]
  
                  sz = stream.read32()
                  self.bones = [[None, None] for i in range(sz)]
  
                  for i in range(sz):
                      initial_transform = [stream.read_float() for i in range(16)]
                      self.bones[i][1] = initial_transform
  
                  sz = stream.read32()
                  self.influences = [[None, None, None] for i in range(sz)]
  
                  for i in range(sz):
                      self.influences[i][0] = stream.read32()
                  for i in range(sz):
                      self.influences[i][1] = stream.read32()
                  for i in range(sz):
                      self.influences[i][2] = stream.read_float()
  
                  sz = stream.read32()
                  assert sz == len(self.bones)
  
                  for i in range(sz):
                      self.bones[i][0] = stream.read32()
  
                  stream.guard()
  
          @classmethod
          def read(cls, stream):
                  mtype = stream.read32() & Mesh.TypeMask
                  mesh = cls(mtype)
  
                  if mtype == Mesh.StandardType:
                          mesh.read_standard_mesh(stream)
                  elif mtype == Mesh.SkinType:
                          mesh.read_skin_mesh(stream)
                  # others here
                  elif mtype == Mesh.NullType:
                          pass
                  else:
                          raise ValueError("don't know how to read {} mesh".format(mtype))
  
                  return mesh
  
  class Material:
          SWrap            = 0x00000001
          TWrap            = 0x00000002
          Translucent      = 0x00000004
          Additive         = 0x00000008
          Subtractive      = 0x00000010
          SelfIlluminating = 0x00000020
          NeverEnvMap      = 0x00000040
          NoMipMap         = 0x00000080
          MipMapZeroBorder = 0x00000100
          IFLMaterial      = 0x08000000
          IFLFrame         = 0x10000000
          DetailMap        = 0x20000000
          BumpMap          = 0x40000000
          ReflectanceMap   = 0x80000000
          AuxiliaryMask    = 0xE0000000
  
          def __init__(self, name="", flags=0,
                  reflectanceMap=-1, bumpMap=-1, detailMap=-1,
                  detailScale=1.0, reflectance=0.0):
                  self.name = name
                  self.flags = flags
                  self.reflectanceMap = reflectanceMap
                  self.bumpMap = bumpMap
                  self.detailMap = detailMap
                  self.detailScale = detailScale
                  self.reflectance = reflectance
  
  def read_bit_set(fd):
          dummy, numWords = unpack("<ii", fd.read(8))
          words = unpack(str(numWords) + "i", fd.read(4 * numWords))
          total = len(words) * 32
          return [(words[i >> 5] & (1 << (i & 31))) != 0 for i in range(total)]
  
  def write_bit_set(fd, bits):
          numWords = int(math.ceil(len(bits) / 32.0))
          words = [0] * numWords
  
          for i, bit in enumerate(bits):
                  if bit:
                          words[i >> 5] |= 1 << (i & 31)
  
          fd.write(pack("<ii", numWords, numWords))
  
          for word in words:
                  fd.write(pack("<i", word))
  
  class Sequence:
          UniformScale = bit(0)
          AlignedScale = bit(1)
          ArbitraryScale = bit(2)
          Blend = bit(3)
          Cyclic = bit(4)
          MakePath = bit(5)
          IflInit = bit(6)
          HasTranslucency = bit(7)
  
          def __init__(self):
                  # todo: get rid of this
                  self.nameIndex = -1
                  self.name = None
                  self.flags = 0
                  self.numKeyframes = 0
                  self.duration = 0
                  self.priority = 0
                  self.firstGroundFrame = 0
                  self.numGroundFrames = 0
                  self.baseRotation = 0
                  self.baseTranslation = 0
                  self.baseScale = 0
                  self.baseObjectState = 0
                  self.baseDecalState = 0
                  self.firstTrigger = 0
                  self.numTriggers = 0
                  self.toolBegin = 0
  
                  self.rotationMatters = []
                  self.translationMatters = []
                  self.scaleMatters = []
                  self.decalMatters = []
                  self.iflMatters = []
                  self.visMatters = []
                  self.frameMatters = []
                  self.matFrameMatters = []
  
          def write(self, fd, writeIndex=True):
                  if writeIndex:
                          fd.write(pack("<i", self.nameIndex))
                  fd.write(pack("<I", self.flags))
                  fd.write(pack("<i", self.numKeyframes))
                  fd.write(pack("<f", self.duration))
                  fd.write(pack("<i", self.priority))
                  fd.write(pack("<i", self.firstGroundFrame))
                  fd.write(pack("<i", self.numGroundFrames))
                  fd.write(pack("<i", self.baseRotation))
                  fd.write(pack("<i", self.baseTranslation))
                  fd.write(pack("<i", self.baseScale))
                  fd.write(pack("<i", self.baseObjectState))
                  fd.write(pack("<i", self.baseDecalState))
                  fd.write(pack("<i", self.firstTrigger))
                  fd.write(pack("<i", self.numTriggers))
                  fd.write(pack("<f", self.toolBegin))
  
                  write_bit_set(fd, self.rotationMatters)
                  write_bit_set(fd, self.translationMatters)
                  write_bit_set(fd, self.scaleMatters)
                  write_bit_set(fd, self.decalMatters)
                  write_bit_set(fd, self.iflMatters)
                  write_bit_set(fd, self.visMatters)
                  write_bit_set(fd, self.frameMatters)
                  write_bit_set(fd, self.matFrameMatters)
  
          @classmethod
          def read_bit_set(cls, fd):
                  dummy = unpack("i", fd.read(4))[0]
                  numWords = unpack("i", fd.read(4))[0]
                  return unpack(str(numWords) + "i", fd.read(4 * numWords))
  
          @classmethod
          def read(cls, fd, readIndex=True):
                  seq = cls()
  
                  if readIndex:
                          seq.nameIndex = unpack("i", fd.read(4))[0]
                  seq.flags = unpack("I", fd.read(4))[0]
                  seq.numKeyframes = unpack("i", fd.read(4))[0]
                  seq.duration = unpack("f", fd.read(4))[0]
                  seq.priority = unpack("i", fd.read(4))[0]
                  seq.firstGroundFrame = unpack("i", fd.read(4))[0]
                  seq.numGroundFrames = unpack("i", fd.read(4))[0]
                  seq.baseRotation = unpack("i", fd.read(4))[0]
                  seq.baseTranslation = unpack("i", fd.read(4))[0]
                  seq.baseScale = unpack("i", fd.read(4))[0]
                  seq.baseObjectState = unpack("i", fd.read(4))[0]
                  seq.baseDecalState = unpack("i", fd.read(4))[0]
                  seq.firstTrigger = unpack("i", fd.read(4))[0]
                  seq.numTriggers = unpack("i", fd.read(4))[0]
                  seq.toolBegin = unpack("f", fd.read(4))[0]
  
                  seq.rotationMatters = read_bit_set(fd)
                  seq.translationMatters = read_bit_set(fd)
                  seq.scaleMatters = read_bit_set(fd)
                  seq.decalMatters = read_bit_set(fd)
                  seq.iflMatters = read_bit_set(fd)
                  seq.visMatters = read_bit_set(fd)
                  seq.frameMatters = read_bit_set(fd)
                  seq.matFrameMatters = read_bit_set(fd)
  
                  return seq
  