123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- """
- quick hack script to convert from quake2 .bsp to simple format to draw. drops
- most information. keeps only raw polys and lightmaps.
- makes .blv and .llv file (big and little endian level)
- file format is
- 'BLV1' or 'LLV1'
- int texwidth
- int texheight
- int numtris
- float startx, starty, startz, startyaw (rads)
- uint32 texdata[texwidth*texheight]
- float texcoords[numtris*6]
- float verts[numtris*9]
- this makes for trivial drawing, but of course it's very inefficient.
- this is very different from the original format which has a bunch of
- optimizations of pvs, culling, uses tris/quads/polys, level-specific data,
- textures, etc. there's also lots of trickery to save space in packing,
- indirection, etc. but we just flatten all that out to triangles. it's probably
- faster these days for such small amounts of geometry anyway.
- http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
- scott.q2convert@h4ck3r.net
- """
- import os
- import sys
- import struct
- def strunpack(stream, format):
- return struct.unpack(format, stream.read(struct.calcsize(format)))
- def die(msg):
- print msg
- raise SystemExit(1)
- def convCoord(v):
- """swizzle, the quake order is wacky. also scale by an arbitrary amount."""
- scale = 1.0/15.0
- return (v[1]*scale, v[2]*scale, v[0]*scale)
- def loadRawData(raw):
- # header
- headerstr = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"
- data = (magic, version,
- off_Entities, len_Entities,
- off_Planes, len_Planes,
- off_Vertices, len_Vertices,
- off_Visibility, len_Visibility,
- off_Nodes, len_Nodes,
- off_Texture_Information, len_Texture_Information,
- off_Faces, len_Faces,
- off_Lightmaps, len_Lightmaps,
- off_Leaves, len_Leaves,
- off_Leaf_Face_Table, len_Leaf_Face_Table,
- off_Leaf_Brush_Table, len_Leaf_Brush_Table,
- off_Edges, len_Edges,
- off_Face_Edge_Table, len_Face_Edge_Table,
- off_Models, len_Models,
- off_Brushes, len_Brushes,
- off_Brush_Sides, len_Brush_Sides,
- off_Pop, len_Pop,
- off_Areas, len_Areas,
- off_Area_Portals, len_Area_Portals) = struct.unpack_from(headerstr, raw)
- if struct.pack("BBBB", magic>>24, (magic>>16)&0xff, (magic>>8)&0xff, magic&0xff) != "PSBI": die("Bad header")
- if version != 38: die("Bad version")
- Leaves = []
- leaf_Size = 28
- for i in range(len_Leaves / leaf_Size):
- (brush_or,
- cluster,
- area,
- bbox_minX, bbox_minY, bbox_minZ,
- bbox_maxX, bbox_maxY, bbox_maxZ,
- first_leaf_face, num_leaf_faces,
- first_leaf_brush, num_leaf_brushes) = struct.unpack_from("IHHhhhhhhHHHH", raw, off_Leaves + i*leaf_Size)
- Leaves.append((first_leaf_face, num_leaf_faces))
- print "Leaves: %d" % len(Leaves)
- Leaf_Face_Table = []
- leafface_Size = 2
- for i in range(len_Leaf_Face_Table / leafface_Size):
- Leaf_Face_Table.append(struct.unpack_from("H", raw, off_Leaf_Face_Table + i*leafface_Size)[0])
- Faces = []
- face_Size = 20
- for i in range(len_Faces / face_Size):
- (plane, plane_Size,
- first_edge, num_edges,
- texture_info,
- style0, style1, style2, style3,
- lightmap_offset) = struct.unpack_from("HHIHHBBBBI", raw, off_Faces + i*face_Size)
- Faces.append((first_edge, num_edges, lightmap_offset))
- print "Faces: %d" % len(Faces)
- Face_Edge_Table = []
- faceedge_Size = 4
- for i in range(len_Face_Edge_Table / faceedge_Size):
- Face_Edge_Table.append(struct.unpack_from("i", raw, off_Face_Edge_Table + i*faceedge_Size)[0])
- Edges = []
- edge_Size = 4
- for i in range(len_Edges / edge_Size):
- (v0, v1) = struct.unpack_from("HH", raw, off_Edges + i*edge_Size)
- Edges.append((v0, v1))
- print "Edges: %d" % len(Edges)
-
- Vertices = []
- vert_Size = 12
- for i in range(len_Vertices / vert_Size):
- v = struct.unpack_from("fff", raw, off_Vertices + i*vert_Size)
- Vertices.append(convCoord(v))
- print "Vertices: %d" % len(Vertices)
- ents = struct.unpack_from("%ds" % len_Entities, raw, off_Entities)[0][1:-3] # opening { and final }+nul
- Entities = []
- for ent in ents.split("}\n{"):
- entdata = {}
- for line in ent.lstrip("\n").rstrip("\n").split("\n"):
- k,v = line.lstrip('"').rstrip('"').split('" "')
- entdata[k] = v
- Entities.append(entdata)
- return Leaves, Leaf_Face_Table, Faces, Face_Edge_Table, Edges, Vertices, Entities
- def writeBinary(triverts, Entities, packprefix, fileext):
- playerStart = findEntity(Entities, "info_player_start")
- sx, sy, sz = playerStart['origin'].split(' ')
- numtris = len(triverts) / 9
- texwidth = 0
- texheight = 0
- startx, starty, startz = convCoord((float(sx), float(sy), float(sz)))
- startyaw = float(playerStart['angle'])
- texcoords = (0.0, 0.0) * numtris * 3
- outname = os.path.splitext(sys.argv[1])[0] + fileext
- print "writing", outname + "...",
- out = open(outname, "wb")
- out.write(struct.pack(packprefix + "4sIIIffff", 'LLV1', texwidth, texheight, numtris, startx, starty, startz, startyaw))
- for i in range(numtris*3):
- out.write(struct.pack(packprefix + "ff", 0.0, 0.0))
- for tv in triverts:
- out.write(struct.pack(packprefix + "f", tv))
- out.close()
- print "done"
- def findEntity(entities, class_name):
- for item in entities:
- if item['classname'] == class_name:
- return item
- def main():
- if len(sys.argv) != 2: die("usage: convert <quake2.bsp>")
- raw = open(sys.argv[1], "rb").read()
- # Leaves are first+len into Leaf_Face_Table
- # Leaf_Face_Table is indices into Faces
- # Faces is first+len into Face_Edge_Table
- # Face_Edge_Table is indices of Edges. if index is positive then (v0,v1) for the edge else (v1,v0)
- # Edges is pairs of indices into Vertices
- # Vertices are list of 3 floats
- #
- # Seems crazily complicated these days (thankfully :)
- Leaves, Leaf_Face_Table, Faces, Face_Edge_Table, Edges, Vertices, Entities = loadRawData(raw)
- triverts = []
- # get all polys
- for first_leaf_face, num_leaf_faces in Leaves:
- for leaf_face_i in range(first_leaf_face, first_leaf_face + num_leaf_faces):
- face_index = Leaf_Face_Table[leaf_face_i]
- first_face_edge, num_face_edges, lightmapoffset = Faces[face_index]
- polyedges = []
- for face_edge_i in range(first_face_edge, first_face_edge + num_face_edges):
- edgei_signed = Face_Edge_Table[face_edge_i]
- #print "edgei:", edgei_signed
- if edgei_signed >= 0:
- e0, e1 = Edges[edgei_signed]
- else:
- e1, e0 = Edges[-edgei_signed]
- v0 = Vertices[e0]
- v1 = Vertices[e1]
- polyedges.append((v0, v1))
- #print "(%f,%f,%f) -> (%f,%f,%f)" % (v0[0], v0[1], v0[2], v1[0], v1[1], v1[2])
- polyverts = []
- for pei in range(len(polyedges)):
- if polyedges[pei][1] != polyedges[(pei+1) % len(polyedges)][0]:
- die("poly isn't normal closed style")
- polyverts.append(polyedges[pei][0])
- for i in range(1, len(polyverts) - 1):
- triverts.extend(polyverts[0])
- triverts.extend(polyverts[i])
- triverts.extend(polyverts[i+1])
- texid.append(lightmapoffset)
- writeBinary(triverts, Entities, "<", ".llv")
- writeBinary(triverts, Entities, ">", ".blv")
- if __name__ == "__main__": main()
|