Skip to content

Commit

Permalink
ASS improvements
Browse files Browse the repository at this point in the history
Support for ASS render_model importing and exporting. Should be possible to edit the Halo 3 Brute now.

Take armature transform into consideration when grabbing keyframe transform from animations.

Add some extra logic to change how physics tags are handled if the target game is Halo 2 for upgrading.

Improve scenario block mutator code. All object type tag blocks should be exchangeable.

Add code to upgrade Halo 1 sky tags to Halo 2.

Add code to transfer settings in a Halo 1 sound tag to Halo 2.

Add code to upgrade a Halo 1 weapon to Halo 2.

Add code to import Halo 2 camera track tags.

Fixed WRL crash.

Disable optimize_geo code as it was causing a crash when editing the Halo 3 scarab. Custom normals are being used now anyways.

Check that shader collection exists on disk before attempting to read.

Test scenario object elements to generate BSP origin index during Halo 1 to Halo 2 scenario upgrades. This requires that you have a Halo 2 BSP at the same path as Halo 1 to get the BSP bounds from. Probably will move to reading the original Halo 1 tag later but this is how it works for now.

Misc cleanup.
  • Loading branch information
General-101 committed May 2, 2024
1 parent 2f4aad5 commit 76ce8d4
Show file tree
Hide file tree
Showing 119 changed files with 4,387 additions and 1,249 deletions.
33 changes: 16 additions & 17 deletions io_scene_halo/file_ass/build_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,25 @@ def build_asset(context, filepath, version, game_version, folder_structure, hidd
)

for idx, material in enumerate(ASS.materials):
material_definition = ''
if not material.permutation == '':
material_definition += '%s' % (material.permutation)

if len(material_definition) > 0:
material_definition += ' '

if not material.region == '':
material_definition += '%s' % (material.region)


file.write(
'\n;MATERIAL %s' % (idx) +
'\n"%s"' % (material.name)
)

if version >= 8: #3
file.write(
'\n"%s %s"\n' % (material.permutation, material.region)
)

else:
file.write(
'\n"%s"\n' % (material.material_effect)
)
file.write(
'\n"%s"\n' % material_definition
)

if version >= 4:
file.write(
Expand Down Expand Up @@ -231,20 +236,14 @@ def build_asset(context, filepath, version, game_version, folder_structure, hidd
file.write('\n')

else:
print("Bad object")
print("Geometry file has an invalid geometry class during build: ", geometry.geo_class)

file.write(
'\n;### INSTANCES ###' +
'\n%s\n' % (len(ASS.instances))
)

for idx, instance in enumerate(ASS.instances):
node_index_list = []
if not instance.object_index == -1:
instance_object = ASS.objects[instance.object_index]
if not instance_object.node_index_list == None:
node_index_list = instance_object.node_index_list

file.write(
'\n;INSTANCE %s' % (idx) +
'\n%s' % (instance.object_index) +
Expand Down Expand Up @@ -281,7 +280,7 @@ def build_asset(context, filepath, version, game_version, folder_structure, hidd
'\n%0.10f\n' % (instance.pivot_transform.scale[0])
)

for node_index in node_index_list:
for node_index in instance.bone_groups:
file.write('%s\n' % node_index)

file.close()
236 changes: 145 additions & 91 deletions io_scene_halo/file_ass/build_scene.py

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions io_scene_halo/file_ass/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ def __init__(self, rotation=(0.0, 0.0, 0.0, 1.0), translation=Vector(), scale=Ve
self.scale = scale

class Material:
def __init__(self, name="", asset_name="", texture_path="", slot=0, lod="", permutation="", region="", material_effect="", material_strings=[]):
def __init__(self, name="", asset_name="", texture_path="", slot=0, lod="", permutation="", region="", material_strings=[]):
self.name = name
self.asset_name = asset_name
self.texture_path = texture_path
self.slot = slot
self.lod = lod
self.permutation = permutation
self.region = region
self.material_effect = material_effect
self.material_strings = material_strings

class Object:
Expand Down
4 changes: 2 additions & 2 deletions io_scene_halo/file_ass/process_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def process_file(filepath):
material_definition_items = material_effect.split()
lod, permutation, region = global_functions.material_definition_parser(True, material_definition_items, 'Default', 'Default')

ASS.materials.append(ASSAsset.Material(scene_name, file_name, None, material, lod, permutation, region, material_effect, material_strings))
ASS.materials.append(ASSAsset.Material(scene_name, file_name, None, material, lod, permutation, region, material_strings))

object_count = int(ASS.next())
for object in range(object_count):
Expand Down Expand Up @@ -174,7 +174,7 @@ def process_file(filepath):
light_properties = ASSAsset.Light(light_type, light_color, intensity, hotspot_size, hotspot_falloff_size, uses_near_attenuation, near_attenuation_start, near_attenuation_end, uses_far_attenuation, far_attenuation_start, far_attenuation_end, light_shape, light_aspect_ratio)

else:
print("Bad object")
print("Geometry file has an invalid geometry class during read: ", geo_class)

ASS.objects.append(ASSAsset.Object(geo_class, xref_path, xref_name, material_index, radius, extents, height, vertices, triangles, node_index_list, light_properties))

Expand Down
35 changes: 19 additions & 16 deletions io_scene_halo/file_ass/process_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app
if not context.view_layer.objects.active == None:
bpy.ops.object.mode_set(mode='OBJECT')

default_region = mesh_processing.get_default_region_permutation_name(game_version)
default_permutation = mesh_processing.get_default_region_permutation_name(game_version)
default_region = ""
default_permutation = ""

limit_value = 0.001

Expand Down Expand Up @@ -227,7 +227,7 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app
geometry_list.append((evaluted_mesh, obj, 'MESH'))

else:
print("Bad object")
print("%s has an out of bounds object_type setting" % obj.name)

else:
obj_data = None
Expand Down Expand Up @@ -274,16 +274,16 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app

original_geo.ass_jms.unique_id = str(generated_id)

ASS.instances.append(ASS.Instance(name='Scene Root', local_transform=ASS.Transform(), pivot_transform=ASS.Transform()))
ASS.instances.append(ASS.Instance(name='Scene Root', local_transform=ASS.Transform(), pivot_transform=ASS.Transform(), bone_groups=[]))
for idx, geometry in enumerate(geometry_list):
verts = []
triangles = []
node_index_list = []

region_index = -1
lod = None
region = default_region
permutation = default_permutation
region = ""
permutation = ""
face_set = (None, default_permutation, default_region)

evaluted_mesh = geometry[0]
Expand Down Expand Up @@ -320,20 +320,21 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app
if geo_class == 'BONE':
is_bone = True
armature = geometry[3]
if original_geo.parent:
parent = original_geo.parent

else:
if original_geo.parent:
parent = original_geo.parent
if original_geo.parent.type == 'ARMATURE':
armature = parent
if original_geo.parent_type == 'BONE':
parent = original_geo.parent.data.bones[original_geo.parent_bone]
parent = original_geo.parent.data.bones.get(original_geo.parent_bone)

else:
parent = original_geo.parent.data.bones[0]

if not parent == None and parent in object_list:
parent_id = instance_list.index(parent) + 1
if not parent == None:
for instance_idx, instance in enumerate(instance_list):
if instance == parent:
parent_id = instance_idx + 1

geo_matrix = global_functions.get_matrix(original_geo, original_geo, True, armature, instance_list, is_bone, version, 'ASS', False, custom_scale, False)
geo_dimensions = global_functions.get_dimensions(geo_matrix, original_geo, version, is_bone, 'ASS', custom_scale)
Expand Down Expand Up @@ -482,16 +483,18 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app
scaled_translation, normal = mesh_processing.process_mesh_export_vert(vertex_data, loop_data, loop_normals, "ASS", object_matrix, custom_scale)
uv_set = mesh_processing.process_mesh_export_uv(evaluted_mesh, "ASS", loop_index, version)
color = mesh_processing.process_mesh_export_color(evaluted_mesh, loop_index, point_idx)
node_influence_count, node_set, node_index_list = mesh_processing.process_mesh_export_weights(vertex_data, armature, original_geo, vertex_groups, instance_list, "ASS")
node_influence_count, node_set = mesh_processing.process_mesh_export_weights(vertex_data, armature, original_geo, vertex_groups, instance_list, "ASS", node_index_list)

verts.append(ASS.Vertex(node_influence_count, node_set, region, scaled_translation, normal, color, uv_set))

original_geo.to_mesh_clear()

else:
print("Bad object")
print("Geometry file has an invalid geometry class during scene processing: ", geo_class)

ASS.objects.append(ASS.Object(geo_class, xref_path, xref_name, material_index, radius, extents, height, verts, triangles, light_properties))

ASS.objects.append(ASS.Object(geo_class, xref_path, xref_name, material_index, radius, extents, height, verts, triangles, node_index_list, light_properties))
ASS.instances[-1].bone_groups = node_index_list

for material in material_list:
material_data = material[0]
Expand All @@ -512,7 +515,7 @@ def process_scene(context, version, game_version, hidden_geo, nonrender_geo, app
if permutation:
permutation = permutation.replace(' ', '_').replace('\t', '_')

ASS.materials.append(ASS.Material(material_name, material_name, texture_path, slot_index, lod, permutation, region, material_data.ass_jms.material_effect, material_lightmap))
ASS.materials.append(ASS.Material(material_name, material_name, texture_path, slot_index, lod, permutation, region, material_lightmap))

# Restore visibility status for all resources
resource_management.restore_collection_visibility(stored_collection_visibility)
Expand Down
2 changes: 1 addition & 1 deletion io_scene_halo/file_jma/build_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def build_asset(context, filepath, report, extension, jma_version, game_title, g
JMA = process_scene(context, extension, jma_version, game_title, generate_checksum, fix_rotations, use_maya_sorting, scale_value)
JMA.version = jma_version
JMA.frame_rate = frame_rate_value

binary = False

if jma_version >= 16395:
Expand Down
5 changes: 3 additions & 2 deletions io_scene_halo/file_jma/process_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,10 @@ def process_scene(context, extension, jma_version, game_title, generate_checksum
is_bone = False
if armature:
is_bone = True

armature_matrix = global_functions.get_matrix(armature, armature, True, None, joined_list, False, jma_version, 'JMA', False, scale_value, fix_rotations)
bone_matrix = global_functions.get_matrix(node, node, True, armature, joined_list, True, jma_version, 'JMA', False, scale_value, fix_rotations)
mesh_dimensions = global_functions.get_dimensions(bone_matrix, node, jma_version, is_bone, 'JMA', scale_value)
full_matrix = armature_matrix @ bone_matrix
mesh_dimensions = global_functions.get_dimensions(full_matrix, node, jma_version, is_bone, 'JMA', scale_value)
rotation = (mesh_dimensions.quaternion[0], mesh_dimensions.quaternion[1], mesh_dimensions.quaternion[2], mesh_dimensions.quaternion[3])
translation = (mesh_dimensions.position[0], mesh_dimensions.position[1], mesh_dimensions.position[2])
scale = (mesh_dimensions.scale[0])
Expand Down
2 changes: 1 addition & 1 deletion io_scene_halo/file_jms/process_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def process_scene(context, version, game_version, generate_checksum, fix_rotatio
scaled_translation, normal = mesh_processing.process_mesh_export_vert(vertex_data, loop_data, loop_normals, "JMS", original_geo_matrix, custom_scale)
uv_set = mesh_processing.process_mesh_export_uv(evaluted_mesh, "JMS", loop_index, version)
color = mesh_processing.process_mesh_export_color(evaluted_mesh, loop_index, point_idx)
node_influence_count, node_set, node_index_list = mesh_processing.process_mesh_export_weights(vertex_data, blend_scene.armature, original_geo, vertex_groups, joined_list, "JMS")
node_influence_count, node_set = mesh_processing.process_mesh_export_weights(vertex_data, blend_scene.armature, original_geo, vertex_groups, joined_list, "JMS")

JMS.vertices.append(JMSAsset.Vertex(node_influence_count, node_set, region, scaled_translation, normal, color, uv_set))

Expand Down
49 changes: 25 additions & 24 deletions io_scene_halo/file_tag/build_scene/build_bsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
WEATHER_POLYHEDRA_TOLERANCE = 0.00001

def point_distance_to_plane(point, normal, dist, round_adjust=0):
'''
'''
Returns distance from the point to the plane.
'''
# NOTE: we're taking into account rounding errors for 32bit floats
Expand All @@ -54,18 +54,18 @@ def point_distance_to_plane(point, normal, dist, round_adjust=0):

def is_point_this_side_of_plane(point, normal, dist, on_plane_ok=False,
side_to_check=True, round_adjust=0):
'''
'''
Returns True if point is on the side of the plane being checked.
If side_to_check == True, point is expected to be on front side of plane.
If side_to_check == False, point is expected to be on back side of plane.
'''
rounded_dist = point_distance_to_plane(point, normal, dist, round_adjust)
return (
(on_plane_ok and rounded_dist == 0) or
(on_plane_ok and rounded_dist == 0) or
(rounded_dist if side_to_check else -rounded_dist) > 0
)

def is_point_on_this_side_of_planes(point, planes, on_plane_ok=False,
def is_point_on_this_side_of_planes(point, planes, on_plane_ok=False,
side_to_check=True, round_adjust=0):
'''
Returns True if point is on the side of every the plane being checked.
Expand Down Expand Up @@ -134,12 +134,12 @@ def get_intersection_point_and_ray_of_planes(plane_a, plane_b):
return ray, pos

def get_point_on_plane(norm, dist):
'''
Solves for an arbitrary point on the plane by rearranging
the plane equation(d = x*a + y*b + z*c) and holding two of
'''
Solves for an arbitrary point on the plane by rearranging
the plane equation(d = x*a + y*b + z*c) and holding two of
the values constant and solve for the third.
'''
# Solve for the axis with the largest magnitude, as it
# Solve for the axis with the largest magnitude, as it
# indicates the other axis values can be held at 0.
# we can simplify from this:
# d = x*a + y*b + z*c
Expand All @@ -159,11 +159,11 @@ def get_point_on_plane(norm, dist):
def find_intersect_point_of_planes(plane_0, plane_1, plane_2):
'''
Returns intersection point of 3 planes as a Vector, or
None if there is no intersection. If any of the planes
None if there is no intersection. If any of the planes
are parallel to each other, there is no intersection.
'''
norm_0, norm_1, norm_2 = plane_0[0], plane_1[0], plane_2[0]

# find the intersection vectors between each plane
cross_01 = norm_0.cross(norm_1)
cross_02 = norm_2.cross(norm_0)
Expand All @@ -172,11 +172,11 @@ def find_intersect_point_of_planes(plane_0, plane_1, plane_2):
# any being ~zero indicates the planes are parallel to each other.
if min(cross_01.magnitude, cross_02.magnitude, cross_12.magnitude) < PLANE_PARALLEL_ANGLE_EPSILON:
return None
# figure out which planes to cross and which to try and intersect

# figure out which planes to cross and which to try and intersect
# with. we're comparing the angle between the planes being crossed
# to ensure we maximize the precision of the crossed vectors.
if (cross_01.magnitude > cross_02.magnitude and
if (cross_01.magnitude > cross_02.magnitude and
cross_01.magnitude > cross_12.magnitude):
plane_a, plane_b, plane_c = plane_0, plane_1, plane_2
elif cross_02.magnitude > cross_12.magnitude:
Expand Down Expand Up @@ -215,7 +215,7 @@ def planes_to_convex_hull_vert_coords(planes, round_adjust=0.000001):
for j in range(plane_ct):
if i == j: continue
for k in range(plane_ct):
if k == i or k == j:
if k == i or k == j:
continue

intersect = find_intersect_point_of_planes(
Expand Down Expand Up @@ -504,15 +504,16 @@ def build_scene(context, LEVEL, game_version, game_title, file_version, fix_rota

else:
shader_collection_dic = {}
shader_collection_path = os.path.join(config.HALO_2_TAG_PATH, r"scenarios\shaders\shader_collections.shader_collections")
shader_collection_file = open(shader_collection_path, "r")
for line in shader_collection_file.readlines():
if not global_functions.string_empty_check(line) and not line.startswith(";"):
split_result = line.split()
if len(split_result) == 2:
prefix = split_result[0]
path = split_result[1]
shader_collection_dic[path] = prefix
shader_collection_path = os.path.join(config.HALO_2_TAG_PATH, r"scenarios\shaders\shader_collections.shader_collections")
if os.path.is_file(shader_collection_path):
shader_collection_file = open(shader_collection_path, "r")
for line in shader_collection_file.readlines():
if not global_functions.string_empty_check(line) and not line.startswith(";"):
split_result = line.split()
if len(split_result) == 2:
prefix = split_result[0]
path = split_result[1]
shader_collection_dic[path] = prefix

materials = []
for material in LEVEL.materials:
Expand Down Expand Up @@ -702,7 +703,7 @@ def build_scene(context, LEVEL, game_version, game_title, file_version, fix_rota
else:
print("Could not find a collection for: %s" % material_path)


if game_title == "halo1":
if H1SurfaceFlags.two_sided in H1SurfaceFlags(surface.flags):
material_name += "%"
Expand Down
10 changes: 5 additions & 5 deletions io_scene_halo/file_tag/build_scene/build_lightmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ def process_mesh(SBSP_ASSET, random_color_gen, tag_block, poop_name, material_co
vertex_map[v0] = vertex_idx
vertices.append(render_data.raw_vertices[v0])
vertex_idx += 1

v1 = triangle_indices[triangle_index + 1]
if vertex_map[v1] == -1:
vertex_map[v1] = vertex_idx
vertices.append(render_data.raw_vertices[v1])
vertex_idx += 1

v2 = triangle_indices[triangle_index + 2]
if vertex_map[v2] == -1:
vertex_map[v2] = vertex_idx
Expand Down Expand Up @@ -93,7 +93,7 @@ def process_mesh(SBSP_ASSET, random_color_gen, tag_block, poop_name, material_co
v0 = triangle_indices[triangle_index]
v1 = triangle_indices[triangle_index + 1]
v2 = triangle_indices[triangle_index + 2]

vertex_list = [vertices[vertex_map[v0]], vertices[vertex_map[v1]], vertices[vertex_map[v2]]]
for vertex_idx, vertex in enumerate(vertex_list):
loop_index = (triangle_start * 3) + triangle_index + vertex_idx
Expand Down Expand Up @@ -136,7 +136,7 @@ def build_clusters(lightmap_group, SBSP_ASSET, level_root, random_color_gen, col
material_count = 0
if not SBSP_ASSET == None:
material_count = len(SBSP_ASSET.materials)

for cluster_idx, cluster in enumerate(lightmap_group.clusters):
cluster_name = "cluster_%s" % cluster_idx
mesh = process_mesh(SBSP_ASSET, random_color_gen, cluster, cluster_name, material_count)
Expand Down Expand Up @@ -165,7 +165,7 @@ def build_poops(lightmap_group, SBSP_ASSET, level_root, random_color_gen, collec
object_mesh = bpy.data.objects.new(ob_name, mesh)
object_mesh.parent = level_root
collection.objects.link(object_mesh)

object_mesh.tag_view.data_type_enum = '16'
object_mesh.tag_view.instance_lightmap_policy_enum = str(instanced_geometry_instance.lightmapping_policy)

Expand Down
Loading

0 comments on commit 76ce8d4

Please sign in to comment.