#! /usr/bin/python # -*- indent-tabs-mode: t -*- # -*- coding: utf-8 -*- # Souvarine souvarine@aliasrobotique.org # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Quake 3 BSP importer. Turn a quake 3 bsp file into a saved soya world. # from struct import * from math import * import sys, os, os.path import soya TESSELATE_LEVEL = 5 BSP_ENTITIES_LUMP = 0 BSP_TEXTURES_LUMP = 1 BSP_PLANES_LUMP = 2 BSP_NODES_LUMP = 3 BSP_LEAFS_LUMP = 4 BSP_LEAF_FACES_LUMP = 5 BSP_LEAF_BRUSHES_LUMP = 6 BSP_MODELS_LUMP = 7 BSP_BRUSHES_LUMP = 8 BSP_BRUSHE_SIDES_LUMP = 9 BSP_VERTEXS_LUMP = 10 BSP_INDICES_LUMP = 11 BSP_EFFECTS_LUMP = 12 BSP_FACES_LUMP = 13 BSP_LIGHTMAPS_LUMP = 14 BSP_LIGHTVOLS_LUMP = 15 BSP_VISDATA_LUMP = 16 NB_BSP_LUMPS = 17 class Q3BSPObject(object): @classmethod def unpack(cls, data): object_list = [] for i in range(0, len(data), cls.size): raw = unpack(cls.format, data[i:i+cls.size]) object_list.append(cls(raw)) return object_list class Q3BSPLumpEntry(Q3BSPObject): format = "<2i" size = calcsize(format) def __init__(self, data): self.offset = data[0] self.length = data[1] class Q3BSPEntitie(Q3BSPObject): entry = 0 @classmethod def unpack(cls, data): data = data.replace('\n', ' ') entitys_list = data.split('} {') entitys_list[0] = entitys_list[0][1:] entitys_list[-1] = entitys_list[-1][:-3] return entitys_list class Q3BSPTexture(Q3BSPObject): format = "<64s2i" size = calcsize(format) entry = 1 def __init__(self, data): texture = data[0].rstrip('\x00') file_index = texture.rfind('/') self.name = texture[file_index+1:] self.flags = data[1] self.contents = data[2] class Q3BSPPlane(Q3BSPObject): format = "<4f" size = calcsize(format) entry = 2 def __init__(self, data): self.a = data[0] self.b = data[2] self.c = -data[1] self.d = data[3] self.normal = soya.Vector(None, self.a, self.b, self.c) class Q3BSPNode(Q3BSPObject): format = "<9i" size = calcsize(format) entry = 3 def __init__(self, data): self.plane = data[0] self.front = data[1] self.back = data[2] self.box_min = data[3:6] self.box_max = data[6:9] class Q3BSPLeaf(Q3BSPObject): format = "<12i" size = calcsize(format) entry = 4 def __init__(self, data): self.cluster = data[0] self.area = data[1] self.box_min_x = data[2] self.box_min_y = data[4] self.box_min_z = -data[3] self.box_max_x = data[5] self.box_max_y = data[7] self.box_max_z = -data[6] self.sphere_x = self.box_max_x - self.box_min_x self.sphere_y = self.box_max_y - self.box_min_y self.sphere_z = self.box_max_z - self.box_min_z dx = self.box_max_x - self.sphere_x dy = self.box_max_y - self.sphere_y dz = self.box_max_z - self.sphere_z self.sphere_r = sqrt(dx*dx + dy*dy + dz*dz) self.leaf_face_start = data[8] self.nb_leaf_face = data[9] self.leaf_brush_start = data[10] self.nb_leaf_brush = data[11] self.brush_indices = [] self.model_part = -1 class Q3BSPLeafFace(Q3BSPObject): format = " 0 and (textures[brushes[leafbrush.brush].texture].contents & 1): leaf.brush_indices.append(leafbrush.brush) # Process every cluster # A cluster is a visible leaf (a leaf with faces inside) # Those cluster will be the parts of our splited model for leaf in leafs: # Discarde leaf wich are not cluster # Invalid leaf, outside the level or inside a wall if leaf.cluster == -1: continue # Zero sized leaf, door or moving plateform if leaf.box_min_x == 0 and leaf.box_max_x == 0: continue # Leaf without faces if leaf.nb_leaf_face == 0: continue # Get a list of all facegroups in that leaf # Discare billboard faces (not implemented) and bad patchs (craps in bsp flies) # BSP files use some kind of facegroup pointer called leaffaces leaf_leaffaces = leaffaces[leaf.leaf_face_start:leaf.leaf_face_start+leaf.nb_leaf_face] leaf_facegroups = [] for leafface in leaf_leaffaces: if facegroups[leafface.face].face_type == 4: continue if facegroups[leafface.face].face_type == 2 and (facegroups[leafface.face].nb_vertex <= 0 or facegroups[leafface.face].patch_size_x <= 0): continue leaf_facegroups.append(facegroups[leafface.face]) # Creat the model part corresponding to the leaf # The model part is a list contaning every facegroups of the leaf model_part = [] for facegroup in leaf_facegroups: model_part.append(facegroup.facegroup_index) model_parts.append(model_part) # This dict will be used when creaing the BSP world #leaf2model_part[leaf] = model_parts.index(model_part) leaf.model_part = model_parts.index(model_part) # Creat a splited model from our world splited_model = soya.SplitedModel(world, model_face_groups, model_parts, 80., 0, None) del(world) # Creat the corresponding BSP world and save it bsp_world = soya.BSPWorld(splited_model, nodes, leafs, planes, brushes, visdata) return bsp_world