def make_fog_plane_jms_models(fog_planes, nodes, make_fans=True, optimize=False): jms_models = [] materials = [JmsMaterial("+unused$", "<none>", "+unused$")] plane_index = 0 for fog_plane in fog_planes: tris = edge_loop_to_tris(len(fog_plane.vertices.STEPTREE), make_fan=make_fans) verts = [ JmsVertex(0, vert[0] * 100, vert[1] * 100, vert[2] * 100, tex_v=1.0) for vert in fog_plane.vertices.STEPTREE ] jms_model = JmsModel("bsp", 0, nodes, materials, (), ("fog_plane_%s" % plane_index, ), verts, tris) if optimize: jms_model.optimize_geometry(True) jms_models.append(jms_model) plane_index += 1 return jms_models
def make_mirror_jms_models(clusters, nodes, make_fans=True): jms_models = [] mirrors = [] for cluster in clusters: mirrors.extend(cluster.mirrors.STEPTREE) mirror_index = 0 for mirror in mirrors: tris = edge_loop_to_tris(len(mirror.vertices.STEPTREE), make_fan=make_fans) verts = [ JmsVertex(0, vert[0] * 100, vert[1] * 100, vert[2] * 100, tex_v=1.0) for vert in mirror.vertices.STEPTREE ] jms_models.append( JmsModel("bsp", 0, nodes, [JmsMaterial(mirror.shader.filepath.split("\\")[-1])], (), ("mirror_%s" % mirror_index, ), verts, tris)) mirror_index += 1 return jms_models
def make_bsp_renderable_jms_models(sbsp_body, base_nodes): jms_models = [] lightmaps = sbsp_body.lightmaps.STEPTREE all_tris = sbsp_body.surfaces.STEPTREE shader_index_by_mat_name = {} mat_indices_by_mat_name = {} shader_mats = [] for i in range(len(lightmaps)): materials = lightmaps[i].materials.STEPTREE for j in range(len(materials)): material = materials[j] mat_name = PureWindowsPath(material.shader.filepath).name.lower() mat_name += "!$" if material.flags.fog_plane else "!" if mat_name not in mat_indices_by_mat_name: shader_index_by_mat_name[mat_name] = len(shader_mats) shader_mats.append(JmsMaterial(mat_name)) mat_indices_by_mat_name[mat_name] = [] shader_mats[-1].shader_path = (shader_mats[-1].shader_path + shader_mats[-1].properties) shader_mats[-1].properties = "" mat_indices_by_mat_name[mat_name].append((i, j)) uncomp_vert_unpacker = PyStruct("<14f").unpack_from for mat_name in sorted(mat_indices_by_mat_name): verts = [] tris = [] for i, j in mat_indices_by_mat_name[mat_name]: material = lightmaps[i].materials.STEPTREE[j] mat_index = shader_index_by_mat_name.get(mat_name) if mat_index is None: continue vert_data = material.uncompressed_vertices.data v_base = len(verts) tris.extend( JmsTriangle(0, mat_index, tri[0] + v_base, tri[2] + v_base, tri[1] + v_base) for tri in all_tris[material.surfaces:material.surfaces + material.surface_count]) for i in range(0, material.vertices_count * 56, 56): x, y, z, ni, nj, nk, bi, bj, bk, ti, tj, tk, u, v =\ uncomp_vert_unpacker(vert_data, i) verts.append( JmsVertex(0, x * 100, y * 100, z * 100, ni, nj, nk, -1, 0, u, 1 - v, 0, bi, bj, bk, ti, tj, tk)) jms_models.append( JmsModel("bsp", 0, base_nodes, shader_mats, [], ("renderable", ), verts, tris)) return jms_models
def make_bsp_lightmap_jms_models(sbsp_body, base_nodes): jms_models = [] lightmaps = sbsp_body.lightmaps.STEPTREE all_tris = sbsp_body.surfaces.STEPTREE shader_index_by_mat_name = {} shader_mats = [] for i in range(len(lightmaps)): lm_index = lightmaps[i].bitmap_index if lm_index not in shader_index_by_mat_name and lm_index >= 0: shader_index_by_mat_name[lm_index] = len(shader_index_by_mat_name) shader_mats.append(JmsMaterial("lightmap_%s" % lm_index)) uncomp_vert_xyz_unpacker = PyStruct("<3f").unpack_from uncomp_vert_ijkuv_unpacker = PyStruct("<5f").unpack_from for lightmap in lightmaps: verts = [] tris = [] mat_index = shader_index_by_mat_name.get(lightmap.bitmap_index, -1) if mat_index < 0: continue for material in lightmap.materials.STEPTREE: v_base = len(verts) tris.extend( JmsTriangle(0, mat_index, tri[0] + v_base, tri[2] + v_base, tri[1] + v_base) for tri in all_tris[material.surfaces:material.surfaces + material.surface_count]) vert_off = 0 lm_vert_off = 56 * material.vertices_count vert_data = material.uncompressed_vertices.data for i in range(material.lightmap_vertices_count): x, y, z = uncomp_vert_xyz_unpacker(vert_data, vert_off) i, j, k, u, v = uncomp_vert_ijkuv_unpacker( vert_data, lm_vert_off) vert_off += 56 lm_vert_off += 20 verts.append( JmsVertex(0, x * 100, y * 100, z * 100, i, j, k, -1, 0, u, 1 - v)) jms_models.append( JmsModel("bsp", 0, base_nodes, shader_mats, [], ("lightmap_%s" % lightmap.bitmap_index, ), verts, tris)) return jms_models
def make_cluster_portal_jms_models(planes, clusters, cluster_portals, nodes, make_fans=True, optimize=False): jms_models = [] materials = [ JmsMaterial("+portal", "<none>", "+portal"), JmsMaterial("+portal&", "<none>", "+portal&") ] materials[1].properties = "" cluster_index = 0 portals_seen = set() verts = [] tris = [] for cluster in clusters: for portal_index in cluster.portals.STEPTREE: if portal_index[0] in portals_seen: continue portals_seen.add(portal_index[0]) portal = cluster_portals[portal_index[0]] shader = 1 if portal.flags.ai_cant_hear_through_this else 0 portal_plane = planes[portal.plane_index] tris.extend( edge_loop_to_tris(len(portal.vertices.STEPTREE), mat_id=shader, base=len(verts), make_fan=make_fans)) verts.extend( JmsVertex( 0, vert[0] * 100, vert[1] * 100, vert[2] * 100, tex_v=1.0) for vert in portal.vertices.STEPTREE) jms_model = JmsModel("bsp", 0, nodes, materials, (), ("cluster_portals", ), verts, tris) if optimize: jms_model.optimize_geometry(True) jms_models.append(jms_model) return jms_models
def planes_to_verts_and_tris(planes, center, region=0, mat_id=0, make_fans=False, round_adjust=0.000001): raw_verts, edge_loops = planes_to_verts_and_edge_loops( planes, center, round_adjust=round_adjust) verts = [ JmsVertex(0, v[0] * 100, v[1] * 100, v[2] * 100, tex_v=1.0) for v in raw_verts ] tris = [] # Calculate verts and triangles from the raw vert positions and edge loops for edge_loop in edge_loops: tris.extend(edge_loop_to_tris(edge_loop, region, mat_id, 0, make_fans)) return verts, tris
mod2_verts_def = BlockDef( raw_reflexive("vertices", mod2_vert_struct, 65535), endian='>' ) mod2_tri_strip_def = BlockDef( raw_reflexive("triangle", mod2_tri_struct, 65535), endian='>' ) LOD_NAMES = ("superhigh", "high", "medium", "low", "superlow") MAX_STRIP_LEN = 32763 * 3 EMPTY_GEOM_VERTS = ( JmsVertex(0, 0.000000001, 0.0, 0.0, 0.0, 0.0, 1.0, -1, 0.0, 0.0, 0.0), JmsVertex(0, 0.0, 0.000000001, 0.0, 0.0, 0.0, 1.0, -1, 0.0, 0.0, 1.0), JmsVertex(0, 0.0, 0.0, 0.000000001, 0.0, 0.0, 1.0, -1, 0.0, 1.0, 0.0), ) def generate_shader(jms_material, tags_dir, data_dir=""): shdr_type = jms_material.shader_type shdr_path = jms_material.shader_path if not shdr_path: return
def jms_model_from_obj(obj_string, model_name=None): if not isinstance(obj_string, str): raise TypeError("Argument must be of type 'str', not '%s'." % type(obj_string)) if model_name is None: model_name = "__unnamed" model = JmsModel(model_name) model.nodes = [JmsNode("frame")] model.regions = ["__unnamed"] mats = model.materials tris = model.tris f_num = 0 mat_num = 0 mat_names = {} v_locs = [] v_uvs = [] v_norms = [] v_datas = {} v_data_by_idx = [] norms_to_calc = {} sg_error = False ngon_error = False # Reminder that obj's use 1 based indexing i = 0 for line in obj_string.split("\n"): i += 1 line = line.lstrip(' ') if not line or line[0] == "#": continue line_parts = line.replace('\t', ' ').split(' ', 1) if len(line_parts) < 2: continue token, line = line_parts if token == "v": loc_data = [d for d in line.split(' ') if d] if len(loc_data) != 3: raise ValueError("Invalid vertex coord on line %s." % i) v_locs.append(loc_data) elif token == "vt": uv_data = [d for d in line.split(' ') if d] if len(uv_data) not in (2, 3): raise ValueError("Invalid vertex texture coord on line %s." % i) v_uvs.append(uv_data) elif token == "vn": norm_data = [d for d in line.split(' ') if d] if len(norm_data) != 3: raise ValueError("Invalid vertex normal on line %s." % i) v_norms.append(norm_data) elif token == "f": tri_data = [d for d in line.split(' ') if d] if len(tri_data) != 3: ngon_error = True continue v_indices = [0, 0, 0] for j in range(len(tri_data)): v_data = tuple(tri_data[j].replace(' ', '').split('/')) if not len(v_data[0]): raise ValueError("Invalid triangle vert description.") if v_data not in v_datas: v_data_by_idx.append(v_data) v_datas[v_data] = len(v_data_by_idx) v_idx = v_datas[v_data] - 1 v_indices[j] = v_idx if len(v_data) < 3 or not v_data[2]: norms_to_calc.setdefault(v_idx, []).append(f_num) tris.append(JmsTriangle(0, mat_num, *v_indices)) f_num += 1 elif token == "usemtl": name = line if name in mat_names: mat_num = mat_names[name] else: mat_num = len(mat_names) mats.append(JmsMaterial(name)) mat_names[name] = mat_num elif token == "s" and not sg_error: sg_error = True else: # dont care about anything else pass if sg_error or ngon_error: if ngon_error: print("Quads and n-gons are not supported.") if sg_error: print( "Smoothing groups not supported! Export with vertex normals.") return model.verts = verts = [None] * len(v_data_by_idx) for i in range(len(v_data_by_idx)): v_data = v_data_by_idx[i] loc_idx = int(v_data[0]) - 1 v_loc = v_locs[loc_idx] pos_x = float(v_loc[0]) pos_y = float(v_loc[1]) pos_z = float(v_loc[2]) if len(v_data) > 1 and v_data[1]: v_uv = v_uvs[int(v_data[1]) - 1] tex_u = float(v_uv[0]) tex_v = float(v_uv[1]) else: tex_u = pos_x tex_v = pos_y if len(v_data) > 2 and v_data[2]: v_norm = v_norms[int(v_data[2]) - 1] norm_i = float(v_norm[0]) norm_j = float(v_norm[1]) norm_k = float(v_norm[2]) else: norm_i = 1 norm_j = norm_k = 0 # We want to rotate the model, so we don't use the vertex coords # exactly as they are given(swap y and z and make z negative). verts[i] = JmsVertex(0, pos_x, -pos_z, pos_y, norm_i, -norm_k, norm_j, -1, 0.0, tex_u, tex_v) sqrt = math.sqrt for i in norms_to_calc: vert = verts[i] i = j = k = 0 face_ct = 0 for f_num in norms_to_calc[i]: face_ct += 1 face = tris[f_num] v0 = verts[face.v0] v1 = verts[face.v1] v2 = verts[face.v2] vax = v1.pos_x - v0.pos_x vay = v1.pos_y - v0.pos_y vaz = v1.pos_z - v0.pos_z vbx = v2.pos_x - v0.pos_x vby = v2.pos_y - v0.pos_y vbz = v2.pos_z - v0.pos_z fi = vay * vbz - vaz * vby fj = vaz * vbx - vax * vbz fk = vax * vby - vay * vbx mag = fi**2 + fj**2 + fk**2 if mag > 0: mag = sqrt(mag) i += fi / mag j += fj / mag k += fk / mag if face_ct: vert.norm_i = i / face_ct vert.norm_j = j / face_ct vert.norm_k = k / face_ct if not mats: mats.append(JmsMaterial("__unnamed")) return model
def extract_model(tagdata, tag_path="", **kw): do_write_jms = kw.get('write_jms', True) if do_write_jms: jms_models = None filepath_base = os.path.join(kw['out_dir'], os.path.dirname(tag_path), "models") else: jms_models = [] filepath_base = "" global_markers = {} materials = [] regions = [] nodes = [] for b in tagdata.markers.STEPTREE: marker_name = b.name for inst in b.marker_instances.STEPTREE: try: region = tagdata.regions.STEPTREE[inst.region_index] except Exception: print("Invalid region index in marker '%s'" % marker_name) continue try: perm = region.permutations.STEPTREE[inst.permutation_index] perm_name = perm.name if (perm.flags.cannot_be_chosen_randomly and not perm_name.startswith("~")): perm_name += "~" except Exception: print("Invalid permutation index in marker '%s'" % marker_name) continue perm_markers = global_markers.setdefault(perm_name, []) trans = inst.translation rot = inst.rotation perm_markers.append( JmsMarker(marker_name, perm_name, inst.region_index, inst.node_index, rot.i, rot.j, rot.k, rot.w, trans.x * 100, trans.y * 100, trans.z * 100, 1.0)) for b in tagdata.nodes.STEPTREE: trans = b.translation rot = b.rotation nodes.append( JmsNode(b.name, b.first_child_node, b.next_sibling_node, rot.i, rot.j, rot.k, rot.w, trans.x * 100, trans.y * 100, trans.z * 100, b.parent_node)) for b in tagdata.shaders.STEPTREE: materials.append( JmsMaterial(b.shader.filepath.split("/")[-1].split("\\")[-1])) markers_by_perm = {} geoms_by_perm_lod_region = {} u_scale = tagdata.base_map_u_scale v_scale = tagdata.base_map_v_scale for region in tagdata.regions.STEPTREE: region_index = len(regions) regions.append(region.name) for perm in region.permutations.STEPTREE: perm_name = perm.name if (perm.flags.cannot_be_chosen_randomly and not perm_name.startswith("~")): perm_name += "~" geoms_by_lod_region = geoms_by_perm_lod_region.setdefault( perm_name, {}) perm_markers = markers_by_perm.setdefault(perm_name, []) if hasattr(perm, "local_markers"): for m in perm.local_markers.STEPTREE: trans = m.translation rot = m.rotation perm_markers.append( JmsMarker(m.name, perm_name, region_index, m.node_index, rot.i, rot.j, rot.k, rot.w, trans.x * 100, trans.y * 100, trans.z * 100, 1.0)) last_geom_index = -1 for lod in range(5): geoms_by_region = geoms_by_lod_region.get(lod, {}) region_geoms = geoms_by_region.get(region.name, []) geom_index = perm[perm.NAME_MAP["superlow_geometry_block"] + (4 - lod)] if (geom_index in region_geoms or geom_index == last_geom_index): continue geoms_by_lod_region[lod] = geoms_by_region geoms_by_region[region.name] = region_geoms region_geoms.append(geom_index) last_geom_index = geom_index try: use_local_nodes = tagdata.flags.parts_have_local_nodes except Exception: use_local_nodes = False def_node_map = list(range(128)) def_node_map.append(-1) # use big endian since it will have been byteswapped comp_vert_unpacker = PyStruct(">3f3I2h2bh").unpack_from uncomp_vert_unpacker = PyStruct(">14f2h2f").unpack_from for perm_name in sorted(geoms_by_perm_lod_region): geoms_by_lod_region = geoms_by_perm_lod_region[perm_name] perm_markers = markers_by_perm.get(perm_name) for lod in sorted(geoms_by_lod_region): if lod == -1: continue jms_name = perm_name + { 4: " superlow", 3: " low", 2: " medium", 1: " high", 0: " superhigh" }.get(lod, "") filepath = os.path.join(filepath_base, jms_name + ".jms") markers = list(perm_markers) markers.extend(global_markers.get(perm_name, ())) verts = [] tris = [] geoms_by_region = geoms_by_lod_region[lod] for region_name in sorted(geoms_by_region): region_index = regions.index(region_name) geoms = geoms_by_region[region_name] for geom_index in geoms: try: geom_block = tagdata.geometries.STEPTREE[geom_index] except Exception: print("Invalid geometry index '%s'" % geom_index) continue for part in geom_block.parts.STEPTREE: v_origin = len(verts) shader_index = part.shader_index try: node_map = list(part.local_nodes) node_map.append(-1) compressed = False except (AttributeError, KeyError): compressed = True if not use_local_nodes: node_map = def_node_map try: unparsed = isinstance(part.triangles.STEPTREE.data, bytearray) except Exception: unparsed = False # TODO: Make this work in meta(parse verts and tris) try: if compressed and unparsed: vert_data = part.compressed_vertices.STEPTREE.data for off in range(0, len(vert_data), 32): v = comp_vert_unpacker(vert_data, off) n = v[3] ni = (n & 1023) / 1023 nj = ((n >> 11) & 1023) / 1023 nk = ((n >> 22) & 511) / 511 if (n >> 10) & 1: ni = ni - 1.0 if (n >> 21) & 1: nj = nj - 1.0 if (n >> 31) & 1: nk = nk - 1.0 verts.append( JmsVertex(v[8] // 3, v[0] * 100, v[1] * 100, v[2] * 100, ni, nj, nk, v[9] // 3, 1.0 - (v[10] / 32767), u_scale * v[6] / 32767, 1.0 - v_scale * v[7] / 32767)) elif compressed: for v in part.compressed_vertices.STEPTREE: n = v[3] ni = (n & 1023) / 1023 nj = ((n >> 11) & 1023) / 1023 nk = ((n >> 22) & 511) / 511 if (n >> 10) & 1: ni = ni - 1.0 if (n >> 21) & 1: nj = nj - 1.0 if (n >> 31) & 1: nk = nk - 1.0 verts.append( JmsVertex(v[8] // 3, v[0] * 100, v[1] * 100, v[2] * 100, ni, nj, nk, v[9] // 3, 1.0 - (v[10] / 32767), u_scale * v[6] / 32767, 1.0 - v_scale * v[7] / 32767)) elif not compressed and unparsed: vert_data = part.uncompressed_vertices.STEPTREE.data for off in range(0, len(vert_data), 68): v = uncomp_vert_unpacker(vert_data, off) verts.append( JmsVertex(node_map[v[14]], v[0] * 100, v[1] * 100, v[2] * 100, v[3], v[4], v[5], node_map[v[15]], max(0, min(1, v[17])), u_scale * v[12], 1.0 - v_scale * v[13])) else: for v in part.uncompressed_vertices.STEPTREE: verts.append( JmsVertex(node_map[v[14]], v[0] * 100, v[1] * 100, v[2] * 100, v[3], v[4], v[5], node_map[v[15]], max(0, min(1, v[17])), u_scale * v[12], 1.0 - v_scale * v[13])) except Exception: print(format_exc()) print("If you see this, tell Moses to stop " "f*****g with the vertex definition.") try: if unparsed: tri_block = part.triangles.STEPTREE.data tri_list = [-1] * (len(tri_block) // 2) for i in range(len(tri_list)): # assuming big endian tri_list[i] = (tri_block[i * 2 + 1] + (tri_block[i * 2] << 8)) if tri_list[i] > 32767: tri_list[i] = -1 else: tri_block = part.triangles.STEPTREE tri_list = [] for triangle in tri_block: tri_list.extend(triangle) swap = True for i in range(len(tri_list) - 2): v0 = tri_list[i] v1 = tri_list[i + 1 + swap] v2 = tri_list[i + 2 - swap] if v0 != -1 and v1 != -1 and v2 != -1: # remove degens if v0 != v1 and v0 != v2 and v1 != v2: tris.append( JmsTriangle( region_index, shader_index, v0 + v_origin, v1 + v_origin, v2 + v_origin)) swap = not swap except Exception: print(format_exc()) print("Could not parse triangle blocks.") jms_model = JmsModel(jms_name, tagdata.node_list_checksum, nodes, materials, markers, regions, verts, tris) if do_write_jms: write_jms(filepath, jms_model) else: jms_models.append(jms_model) return jms_models
def make_bsp_jms_verts(bsp, transform=None): verts = [ JmsVertex(0, v[0] * 100, v[1] * 100, v[2] * 100, tex_v=1.0) for v in bsp.vertices.STEPTREE ] return verts