q2bspload.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. """
  2. quick hack script to convert from quake2 .bsp to simple format to draw. drops
  3. most information. keeps only raw polys and lightmaps.
  4. makes .blv and .llv file (big and little endian level)
  5. file format is
  6. 'BLV1' or 'LLV1'
  7. int texwidth
  8. int texheight
  9. int numtris
  10. float startx, starty, startz, startyaw (rads)
  11. uint32 texdata[texwidth*texheight]
  12. float texcoords[numtris*6]
  13. float verts[numtris*9]
  14. this makes for trivial drawing, but of course it's very inefficient.
  15. this is very different from the original format which has a bunch of
  16. optimizations of pvs, culling, uses tris/quads/polys, level-specific data,
  17. textures, etc. there's also lots of trickery to save space in packing,
  18. indirection, etc. but we just flatten all that out to triangles. it's probably
  19. faster these days for such small amounts of geometry anyway.
  20. http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
  21. scott.q2convert@h4ck3r.net
  22. """
  23. import os
  24. import sys
  25. import struct
  26. def strunpack(stream, format):
  27. return struct.unpack(format, stream.read(struct.calcsize(format)))
  28. def die(msg):
  29. print msg
  30. raise SystemExit(1)
  31. def convCoord(v):
  32. """swizzle, the quake order is wacky. also scale by an arbitrary amount."""
  33. scale = 1.0/15.0
  34. return (v[1]*scale, v[2]*scale, v[0]*scale)
  35. def loadRawData(raw):
  36. # header
  37. headerstr = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"
  38. data = (magic, version,
  39. off_Entities, len_Entities,
  40. off_Planes, len_Planes,
  41. off_Vertices, len_Vertices,
  42. off_Visibility, len_Visibility,
  43. off_Nodes, len_Nodes,
  44. off_Texture_Information, len_Texture_Information,
  45. off_Faces, len_Faces,
  46. off_Lightmaps, len_Lightmaps,
  47. off_Leaves, len_Leaves,
  48. off_Leaf_Face_Table, len_Leaf_Face_Table,
  49. off_Leaf_Brush_Table, len_Leaf_Brush_Table,
  50. off_Edges, len_Edges,
  51. off_Face_Edge_Table, len_Face_Edge_Table,
  52. off_Models, len_Models,
  53. off_Brushes, len_Brushes,
  54. off_Brush_Sides, len_Brush_Sides,
  55. off_Pop, len_Pop,
  56. off_Areas, len_Areas,
  57. off_Area_Portals, len_Area_Portals) = struct.unpack_from(headerstr, raw)
  58. if struct.pack("BBBB", magic>>24, (magic>>16)&0xff, (magic>>8)&0xff, magic&0xff) != "PSBI": die("Bad header")
  59. if version != 38: die("Bad version")
  60. Leaves = []
  61. leaf_Size = 28
  62. for i in range(len_Leaves / leaf_Size):
  63. (brush_or,
  64. cluster,
  65. area,
  66. bbox_minX, bbox_minY, bbox_minZ,
  67. bbox_maxX, bbox_maxY, bbox_maxZ,
  68. first_leaf_face, num_leaf_faces,
  69. first_leaf_brush, num_leaf_brushes) = struct.unpack_from("IHHhhhhhhHHHH", raw, off_Leaves + i*leaf_Size)
  70. Leaves.append((first_leaf_face, num_leaf_faces))
  71. print "Leaves: %d" % len(Leaves)
  72. Leaf_Face_Table = []
  73. leafface_Size = 2
  74. for i in range(len_Leaf_Face_Table / leafface_Size):
  75. Leaf_Face_Table.append(struct.unpack_from("H", raw, off_Leaf_Face_Table + i*leafface_Size)[0])
  76. Faces = []
  77. face_Size = 20
  78. for i in range(len_Faces / face_Size):
  79. (plane, plane_Size,
  80. first_edge, num_edges,
  81. texture_info,
  82. style0, style1, style2, style3,
  83. lightmap_offset) = struct.unpack_from("HHIHHBBBBI", raw, off_Faces + i*face_Size)
  84. Faces.append((first_edge, num_edges, lightmap_offset))
  85. print "Faces: %d" % len(Faces)
  86. Face_Edge_Table = []
  87. faceedge_Size = 4
  88. for i in range(len_Face_Edge_Table / faceedge_Size):
  89. Face_Edge_Table.append(struct.unpack_from("i", raw, off_Face_Edge_Table + i*faceedge_Size)[0])
  90. Edges = []
  91. edge_Size = 4
  92. for i in range(len_Edges / edge_Size):
  93. (v0, v1) = struct.unpack_from("HH", raw, off_Edges + i*edge_Size)
  94. Edges.append((v0, v1))
  95. print "Edges: %d" % len(Edges)
  96. Vertices = []
  97. vert_Size = 12
  98. for i in range(len_Vertices / vert_Size):
  99. v = struct.unpack_from("fff", raw, off_Vertices + i*vert_Size)
  100. Vertices.append(convCoord(v))
  101. print "Vertices: %d" % len(Vertices)
  102. ents = struct.unpack_from("%ds" % len_Entities, raw, off_Entities)[0][1:-3] # opening { and final }+nul
  103. Entities = []
  104. for ent in ents.split("}\n{"):
  105. entdata = {}
  106. for line in ent.lstrip("\n").rstrip("\n").split("\n"):
  107. k,v = line.lstrip('"').rstrip('"').split('" "')
  108. entdata[k] = v
  109. Entities.append(entdata)
  110. return Leaves, Leaf_Face_Table, Faces, Face_Edge_Table, Edges, Vertices, Entities
  111. def writeBinary(triverts, Entities, packprefix, fileext):
  112. playerStart = findEntity(Entities, "info_player_start")
  113. sx, sy, sz = playerStart['origin'].split(' ')
  114. numtris = len(triverts) / 9
  115. texwidth = 0
  116. texheight = 0
  117. startx, starty, startz = convCoord((float(sx), float(sy), float(sz)))
  118. startyaw = float(playerStart['angle'])
  119. texcoords = (0.0, 0.0) * numtris * 3
  120. outname = os.path.splitext(sys.argv[1])[0] + fileext
  121. print "writing", outname + "...",
  122. out = open(outname, "wb")
  123. out.write(struct.pack(packprefix + "4sIIIffff", 'LLV1', texwidth, texheight, numtris, startx, starty, startz, startyaw))
  124. for i in range(numtris*3):
  125. out.write(struct.pack(packprefix + "ff", 0.0, 0.0))
  126. for tv in triverts:
  127. out.write(struct.pack(packprefix + "f", tv))
  128. out.close()
  129. print "done"
  130. def findEntity(entities, class_name):
  131. for item in entities:
  132. if item['classname'] == class_name:
  133. return item
  134. def main():
  135. if len(sys.argv) != 2: die("usage: convert <quake2.bsp>")
  136. raw = open(sys.argv[1], "rb").read()
  137. # Leaves are first+len into Leaf_Face_Table
  138. # Leaf_Face_Table is indices into Faces
  139. # Faces is first+len into Face_Edge_Table
  140. # Face_Edge_Table is indices of Edges. if index is positive then (v0,v1) for the edge else (v1,v0)
  141. # Edges is pairs of indices into Vertices
  142. # Vertices are list of 3 floats
  143. #
  144. # Seems crazily complicated these days (thankfully :)
  145. Leaves, Leaf_Face_Table, Faces, Face_Edge_Table, Edges, Vertices, Entities = loadRawData(raw)
  146. triverts = []
  147. # get all polys
  148. for first_leaf_face, num_leaf_faces in Leaves:
  149. for leaf_face_i in range(first_leaf_face, first_leaf_face + num_leaf_faces):
  150. face_index = Leaf_Face_Table[leaf_face_i]
  151. first_face_edge, num_face_edges, lightmapoffset = Faces[face_index]
  152. polyedges = []
  153. for face_edge_i in range(first_face_edge, first_face_edge + num_face_edges):
  154. edgei_signed = Face_Edge_Table[face_edge_i]
  155. #print "edgei:", edgei_signed
  156. if edgei_signed >= 0:
  157. e0, e1 = Edges[edgei_signed]
  158. else:
  159. e1, e0 = Edges[-edgei_signed]
  160. v0 = Vertices[e0]
  161. v1 = Vertices[e1]
  162. polyedges.append((v0, v1))
  163. #print "(%f,%f,%f) -> (%f,%f,%f)" % (v0[0], v0[1], v0[2], v1[0], v1[1], v1[2])
  164. polyverts = []
  165. for pei in range(len(polyedges)):
  166. if polyedges[pei][1] != polyedges[(pei+1) % len(polyedges)][0]:
  167. die("poly isn't normal closed style")
  168. polyverts.append(polyedges[pei][0])
  169. for i in range(1, len(polyverts) - 1):
  170. triverts.extend(polyverts[0])
  171. triverts.extend(polyverts[i])
  172. triverts.extend(polyverts[i+1])
  173. texid.append(lightmapoffset)
  174. writeBinary(triverts, Entities, "<", ".llv")
  175. writeBinary(triverts, Entities, ">", ".blv")
  176. if __name__ == "__main__": main()