def spiderman(bsp_path): tag = sbsp_def.build(filepath=bsp_path) bsp = tag.data.tagdata bsp_surfaces = bsp.collision_bsp.collision_bsp_array[ 0].surfaces.surfaces_array for surface in bsp_surfaces: surface.flags.climbable = True tag.serialize(backup=False, temp=False)
def fix_bsp(bsp_tag_path, report_only=False): tag = sbsp_def.build(filepath=bsp_tag_path) collision_bsp = tag.data.tagdata.collision_bsp.collision_bsp_array[0] bsp3d_nodes = collision_bsp.bsp3d_nodes.bsp3d_nodes_array bsp2d_nodes = collision_bsp.bsp2d_nodes.bsp2d_nodes_array bsp2d_refs = collision_bsp.bsp2d_references.bsp2d_references_array leaves = collision_bsp.leaves.leaves_array planes = collision_bsp.planes.planes_array surfaces = collision_bsp.surfaces.surfaces_array edges = collision_bsp.edges.edges_array verts = collision_bsp.vertices.vertices_array def gather_bsp2d_node_surfaces(bsp2d_node_index): if bsp2d_node_index & 0x80000000 != 0: surface_index = bsp2d_node_index & 0x7FFFFFFF return [surface_index] bsp2d_node = bsp2d_nodes[bsp2d_node_index] left_surfaces = gather_bsp2d_node_surfaces(bsp2d_node.left_child) right_surfaces = gather_bsp2d_node_surfaces(bsp2d_node.right_child) return left_surfaces + right_surfaces def gather_bsp2d_ref_surfaces(bsp2d_ref_index): bsp2d_ref = bsp2d_refs[bsp2d_ref_index] return gather_bsp2d_node_surfaces(bsp2d_ref.bsp2d_node) def gather_leaf_surfaces(leaf_index): leaf = leaves[leaf_index] bsp2d_ref_count = leaf.bsp2d_reference_count bsp2d_ref_first = leaf.first_bsp2d_reference surfaces = [] for r in range(bsp2d_ref_first, bsp2d_ref_first + bsp2d_ref_count): surfaces += gather_bsp2d_ref_surfaces(r) return surfaces def gather_bsp3d_node_surfaces(bsp3d_node_index): if bsp3d_node_index == -1: return [] elif bsp3d_node_index & 0x80000000 != 0: leaf_index = bsp3d_node_index & 0x7FFFFFFF return gather_leaf_surfaces(leaf_index) bsp3d_node = bsp3d_nodes[bsp3d_node_index] front_surfaces = gather_bsp3d_node_surfaces(bsp3d_node.front_child) back_surfaces = gather_bsp3d_node_surfaces(bsp3d_node.back_child) return front_surfaces + back_surfaces def get_extended_surface_outer_edges(plane_surface_index, surface_indices): unvisited_surface_indices = set([plane_surface_index]) visited_surface_indices = set() outer_edge_indices = set() surface_indices = set(surface_indices) while len(unvisited_surface_indices) > 0: surface_index = unvisited_surface_indices.pop() visited_surface_indices.add(surface_index) surface = surfaces[surface_index] curr_edge_index = surface.first_edge while True: curr_edge = edges[curr_edge_index] forward = curr_edge.left_surface == surface_index neighbour_surface_index = curr_edge.right_surface if forward else curr_edge.left_surface if neighbour_surface_index in surface_indices: if neighbour_surface_index not in visited_surface_indices: unvisited_surface_indices.add(neighbour_surface_index) else: outer_edge_indices.add(curr_edge_index) next_edge_index = curr_edge[ "forward_edge" if forward else "reverse_edge"] if next_edge_index == surface.first_edge: break curr_edge_index = next_edge_index return outer_edge_indices # True if the edge passes through the convex polyhedron defined by node # plane half-spaces. It is not considered inside of it is co-planar. # See: http://web.cse.ohio-state.edu/~parent.1/classes/681/Lectures/14.ObjectIntersection.pdf def edge_inside_polyhedron(edge_index, halfspaces): edge = edges[edge_index] edge_start = to_np_point(verts[edge.start_vertex]) edge_end = to_np_point(verts[edge.end_vertex]) t_entry_max = None t_exit_min = None for plane_index, is_front, _bsp3d_node in halfspaces: plane = to_np_plane(planes[plane_index & 0x7FFFFFFF], is_front) start_cmp = point_cmp_plane(edge_start, plane) end_cmp = point_cmp_plane(edge_end, plane) # If line is co-planar with one of the planes it cannot be inside the # volume. This thredshold is super sensitive... too high and we miss # phantom BSP on very similar planes to parent node planes, but too # low and we get false positive phantom BSP from low precision. if zeroish(start_cmp, 0.0001) and zeroish(end_cmp, 0.0001): return False t_intersection, entering = t_line_plane_intersection( edge_start, edge_end, plane) if t_intersection is not None: if entering: if t_entry_max is None or t_intersection > t_entry_max: t_entry_max = t_intersection else: if t_exit_min is None or t_intersection < t_exit_min: t_exit_min = t_intersection # If we miss the planes then there's no way it can be interecting the volume if t_entry_max is None or t_exit_min is None: return False entry_point = lerp_points(edge_start, edge_end, t_entry_max) exit_point = lerp_points(edge_start, edge_end, t_exit_min) # If entry and exit are essentially the same point, ignore if zeroish(dist(entry_point, exit_point), 0.05): return False # We still need to check if the intersection points are outside the volume for plane_index, is_front, _bsp3d_node in halfspaces: plane = to_np_plane(planes[plane_index & 0x7FFFFFFF], is_front) entry_cmp = point_cmp_plane(entry_point, plane) exit_cmp = point_cmp_plane(exit_point, plane) if not zeroish(entry_cmp, 0.00001) and entry_cmp < 0.0: return False if not zeroish(exit_cmp, 0.00001) and exit_cmp < 0.0: return False return t_entry_max < t_exit_min # Determines if the plane is not obstructed by the surfaces under this node def plane_unoccluded(dividing_plane_index, child_bsp3d_node_index, bsp3d_node_index, parent_halfspaces): # meaning of flagged plane index is unknown... if dividing_plane_index & 0x80000000: dividing_plane_index = dividing_plane_index & 0x7FFFFFFF print("UNEXPECTED FLAGGED PLANE: Converting to " + str(dividing_plane_index)) # first we need the set of all surface indices under this node surface_indices = gather_bsp3d_node_surfaces(child_bsp3d_node_index) # Next, identify which surface was used to define parent node's plane plane_surface_index = None for s in surface_indices: surface = surfaces[s] if surface.plane & 0x7FFFFFFF == dividing_plane_index: plane_surface_index = s if plane_surface_index is None: return False # Get the bounding edges of the extended surface which was used for the plane outer_edge_indices = get_extended_surface_outer_edges( plane_surface_index, surface_indices) # todo: ignore cases where surrounding faces are convex # If any of the bounding edges pass inside the node's space, then the parent plane is exposed for outer_edge_index in outer_edge_indices: if edge_inside_polyhedron(outer_edge_index, parent_halfspaces): print( "Phantom BSP detected: plane={} surface={} edge={} leaf={} path={}/{}" .format( dividing_plane_index, plane_surface_index, outer_edge_index, child_bsp3d_node_index & 0x7FFFFFFF, "/".join([ str(n & 0x7FFFFFFF) for (_p, _f, n) in parent_halfspaces ]), bsp3d_node_index)) return True return False # Recurses through bsp3d nodes detecting and fixing phantom BSP def find_phantom_and_fix(bsp3d_node_index, parent_halfspaces): # Ignore leaves (flagged) and non-existent nodes (-1) if bsp3d_node_index & 0x80000000 != 0: return bsp3d_node = bsp3d_nodes[bsp3d_node_index] front_halfspaces = parent_halfspaces + [ (bsp3d_node.plane, True, bsp3d_node_index) ] back_halfspaces = parent_halfspaces + [ (bsp3d_node.plane, False, bsp3d_node_index) ] # Currently just checking for -1 back child and leaf front child, the # most common case. What a -1 front child looks like is unknown and how # to fix it (if it even needs fixing) is TBD with more research. if bsp3d_node.back_child == -1 and bsp3d_node.front_child & 0x80000000 != 0: if plane_unoccluded(bsp3d_node.plane, bsp3d_node.front_child, bsp3d_node_index, parent_halfspaces): if not report_only: bsp3d_node.back_child = bsp3d_node.front_child find_phantom_and_fix(bsp3d_node.front_child, front_halfspaces) # We may have set the child indices to be the same, so don't need to fix twice if bsp3d_node.back_child != bsp3d_node.front_child: find_phantom_and_fix(bsp3d_node.back_child, back_halfspaces) find_phantom_and_fix(0, []) if not report_only: tag.serialize(backup=False, temp=False)
def offset_level(bsp_path, scenario_path, offset): bsp_tag = sbsp_def.build(filepath=bsp_path) bsp = bsp_tag.data.tagdata scenario_tag = scnr_def.build(filepath=scenario_path) scenario = scenario_tag.data.tagdata for collision_bsp in bsp.collision_bsp.STEPTREE: for plane in collision_bsp.planes.STEPTREE: offset_plane(plane, offset) for vert in collision_bsp.vertices.STEPTREE: offset_point(vert, offset) for lightmap in bsp.lightmaps.STEPTREE: for material in lightmap.materials.STEPTREE: # material planes not included because zero length normals offset_point(material.centroid, offset) vert_count = material.vertices_count vert_buffer = material.uncompressed_vertices.STEPTREE for i in range(vert_count): vert_offset = i * 56 x, y, z = vert_unpacker(vert_buffer[vert_offset:vert_offset + 12]) vert_packer(vert_buffer, vert_offset, x + offset[0], y + offset[1], z + offset[2]) for flare_marker in bsp.lens_flare_markers.STEPTREE: offset_point(flare_marker.position, offset) for cluster in bsp.clusters.STEPTREE: for subcluster in cluster.subclusters.STEPTREE: subcluster.world_bounds_x[0] += offset[0] subcluster.world_bounds_x[1] += offset[0] subcluster.world_bounds_y[0] += offset[1] subcluster.world_bounds_y[1] += offset[1] subcluster.world_bounds_z[0] += offset[2] subcluster.world_bounds_z[1] += offset[2] for mirror in cluster.mirrors.STEPTREE: offset_plane(mirror.plane, offset) for vert in mirror.vertices.STEPTREE: offset_point(vert, offset) for cluster_portal in bsp.cluster_portals.STEPTREE: offset_point(cluster_portal.centroid, offset) for vert in cluster_portal.vertices.STEPTREE: offset_point(vert, offset) for surface in bsp.breakable_surfaces.STEPTREE: offset_point(surface.centroid, offset) for fog_plane in bsp.fog_planes.STEPTREE: offset_plane(fog_plane.plane, offset) for vert in fog_plane.vertices.STEPTREE: offset_point(vert, offset) for weather_polyhedra in bsp.weather_polyhedras.STEPTREE: offset_point(weather_polyhedra.bounding_sphere_center, offset) for plane in weather_polyhedra.planes.STEPTREE: offset_plane(plane, offset) for marker in bsp.markers.STEPTREE: offset_point(marker.position, offset) for decal in bsp.runtime_decals.STEPTREE: offset_point(decal.position, offset) for leaf_map_portal in bsp.leaf_map_portals.STEPTREE: for vert in leaf_map_portal.vertices.STEPTREE: offset_point(vert, offset) bsp.world_bounds_x[0] += offset[0] bsp.world_bounds_x[1] += offset[0] bsp.world_bounds_y[0] += offset[1] bsp.world_bounds_y[1] += offset[1] bsp.world_bounds_z[0] += offset[2] bsp.world_bounds_z[1] += offset[2] bsp.vehicle_floor += offset[2] bsp.vehicle_ceiling += offset[2] for player_spawn in scenario.player_starting_locations.STEPTREE: offset_point(player_spawn.position, offset) bsp_tag.serialize(backup=False, temp=False) scenario_tag.serialize(backup=False, temp=False)
from reclaimer.hek.defs.sbsp import sbsp_def from inspect import getmembers import collada import numpy as np from scipy.spatial.transform import Rotation as R #https://github.com/Sigmmma/reclaimer/blob/master/reclaimer/hek/defs/coll.py bsp_path = "./dangercanyon.scenario_structure_bsp" bsp = sbsp_def.build(filepath=bsp_path).data.tagdata bsp3d_nodes = bsp.collision_bsp.collision_bsp_array[0].bsp3d_nodes.bsp3d_nodes_array bsp2d_nodes = bsp.collision_bsp.collision_bsp_array[0].bsp2d_nodes.bsp2d_nodes_array bsp2d_references = bsp.collision_bsp.collision_bsp_array[0].bsp2d_references.bsp2d_references_array bsp_leaves = bsp.collision_bsp.collision_bsp_array[0].leaves.leaves_array bsp_planes = bsp.collision_bsp.collision_bsp_array[0].planes.planes_array bsp_surfaces = bsp.collision_bsp.collision_bsp_array[0].surfaces.surfaces_array bsp_edges = bsp.collision_bsp.collision_bsp_array[0].edges.edges_array bsp_verts = bsp.collision_bsp.collision_bsp_array[0].vertices.vertices_array dae = collada.Collada() vert_floats = [[v.x, v.y, v.z] for v in bsp_verts] normal_floats = [[p.i, p.j, p.k] for p in bsp_planes] mtl_effect_surface = collada.material.Effect("mtl_effect_surface", [], "phong", diffuse=(0.5, 0.5, 0.5), specular=(0, 1, 0)) mtl_surface = collada.material.Material("mtl_surface", "mtl_surface", mtl_effect_surface) dae.effects.append(mtl_effect_surface) dae.materials.append(mtl_surface) sfc_count = 0 def gen_surface_geometry(bsp_vert_indices, bsp_plane_index, surface_name):
def sbsp_to_mod2(sbsp_path, include_lens_flares=True, include_markers=True, include_weather_polyhedra=True, include_fog_planes=True, include_portals=True, include_collision=True, include_renderable=True, include_mirrors=True, include_lightmaps=True, fan_weather_polyhedra=True, fan_fog_planes=True, fan_portals=True, fan_collision=True, fan_mirrors=True, optimize_fog_planes=False, optimize_portals=False, weather_polyhedra_tolerance=0.0000001): print(" Loading sbsp tag...") sbsp_tag = sbsp_def.build(filepath=sbsp_path) mod2_tag = mod2_def.build() sbsp_body = sbsp_tag.data.tagdata coll_mats = [ JmsMaterial(mat.shader.filepath.split("\\")[-1]) for mat in sbsp_body.collision_materials.STEPTREE ] base_nodes = [JmsNode("frame")] jms_models = [] if include_markers: print(" Converting markers...") try: jms_models.append( make_marker_jms_model(sbsp_body.markers.STEPTREE, base_nodes)) except Exception: print(format_exc()) print(" Could not convert markers") if include_lens_flares: print(" Converting lens flares...") try: jms_models.append( make_lens_flare_jms_model( sbsp_body.lens_flare_markers.STEPTREE, sbsp_body.lens_flares.STEPTREE, base_nodes)) except Exception: print(format_exc()) print(" Could not convert lens flares") if include_fog_planes: print(" Converting fog planes...") try: jms_models.extend( make_fog_plane_jms_models(sbsp_body.fog_planes.STEPTREE, base_nodes, fan_fog_planes, optimize_fog_planes)) except Exception: print(format_exc()) print(" Could not convert fog planes") if include_mirrors: print(" Converting mirrors...") try: jms_models.extend( make_mirror_jms_models(sbsp_body.clusters.STEPTREE, base_nodes, fan_mirrors)) except Exception: print(format_exc()) print(" Could not convert mirrors") if include_portals and sbsp_body.collision_bsp.STEPTREE: print(" Converting portals...") try: jms_models.extend( make_cluster_portal_jms_models( sbsp_body.collision_bsp.STEPTREE[0].planes.STEPTREE, sbsp_body.clusters.STEPTREE, sbsp_body.cluster_portals.STEPTREE, base_nodes, fan_portals, optimize_portals)) except Exception: print(format_exc()) print(" Could not convert portals") if include_weather_polyhedra: print(" Converting weather polyhedra...") try: jms_models.extend( make_weather_polyhedra_jms_models( sbsp_body.weather_polyhedras.STEPTREE, base_nodes, fan_weather_polyhedra, weather_polyhedra_tolerance)) except Exception: print(format_exc()) print(" Could not convert weather polyhedra") if include_collision: print(" Converting collision...") try: jms_models.extend( make_bsp_coll_jms_models(sbsp_body.collision_bsp.STEPTREE, coll_mats, base_nodes, None, False, fan_collision)) except Exception: print(format_exc()) print(" Could not convert collision") if include_renderable: print(" Converting renderable...") try: jms_models.extend( make_bsp_renderable_jms_models(sbsp_body, base_nodes)) except Exception: print(format_exc()) print(" Could not convert renderable") if include_lightmaps: print(" Converting lightmaps...") try: jms_models.extend( make_bsp_lightmap_jms_models(sbsp_body, base_nodes)) except Exception: print(format_exc()) print(" Could not convert lightmaps") print(" Compiling gbxmodel...") mod2_tag.filepath = str(Path(sbsp_path).with_suffix('')) + "_SBSP.gbxmodel" compile_gbxmodel(mod2_tag, MergedJmsModel(*jms_models), True) return mod2_tag