From 04f440b7cc54ed822c0b213278ad50dab03cc396 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Thu, 14 Jul 2022 23:38:29 -0400 Subject: [PATCH] improvements to custom level blender import (#1649) --- .../blender/gltf2_blender_extract.py | 621 ++++++++++++++++++ custom_levels/blender/opengoal.py | 119 ++++ game/mips2c/functions/collide_cache.cpp | 15 +- .../jak1/engine/collide/collide-cache-h.gc | 15 + goal_src/jak1/engine/collide/collide-cache.gc | 2 +- goalc/build_level/collide_common.h | 4 +- goalc/build_level/gltf_mesh_extract.cpp | 70 +- 7 files changed, 833 insertions(+), 13 deletions(-) create mode 100644 custom_levels/blender/gltf2_blender_extract.py create mode 100644 custom_levels/blender/opengoal.py diff --git a/custom_levels/blender/gltf2_blender_extract.py b/custom_levels/blender/gltf2_blender_extract.py new file mode 100644 index 0000000000..5bf09a9198 --- /dev/null +++ b/custom_levels/blender/gltf2_blender_extract.py @@ -0,0 +1,621 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2018-2021 The glTF-Blender-IO authors. + +# Modified for OpenGOAL GLTF extraction. +# Make sure that no meshes have face corner colors. All colors must be vertex colors (float). +# This should replace /3.2/scripts/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py + +import numpy as np +from mathutils import Vector + +from . import gltf2_blender_export_keys +from ...io.com.gltf2_io_debug import print_console +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins + + +def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings): + """Extract primitives from a mesh.""" + print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) + + blender_object = None + if uuid_for_skined_data: + blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object + + use_normals = export_settings[gltf2_blender_export_keys.NORMALS] + if use_normals: + blender_mesh.calc_normals_split() + + use_tangents = False + if use_normals and export_settings[gltf2_blender_export_keys.TANGENTS]: + if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: + try: + blender_mesh.calc_tangents() + use_tangents = True + except Exception: + print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') + + tex_coord_max = 0 + if export_settings[gltf2_blender_export_keys.TEX_COORDS]: + if blender_mesh.uv_layers.active: + tex_coord_max = len(blender_mesh.uv_layers) + + color_max = 0 + if export_settings[gltf2_blender_export_keys.COLORS]: + # changed: now reading from color attributes + color_max = len(blender_mesh.color_attributes) + + armature = None + skin = None + if blender_vertex_groups and export_settings[gltf2_blender_export_keys.SKINS]: + if modifiers is not None: + modifiers_dict = {m.type: m for m in modifiers} + if "ARMATURE" in modifiers_dict: + modifier = modifiers_dict["ARMATURE"] + armature = modifier.object + + # Skin must be ignored if the object is parented to a bone of the armature + # (This creates an infinite recursive error) + # So ignoring skin in that case + is_child_of_arma = ( + armature and + blender_object and + blender_object.parent_type == "BONE" and + blender_object.parent.name == armature.name + ) + if is_child_of_arma: + armature = None + + if armature: + skin = gltf2_blender_gather_skins.gather_skin(export_settings['vtree'].nodes[uuid_for_skined_data].armature, export_settings) + if not skin: + armature = None + + use_morph_normals = use_normals and export_settings[gltf2_blender_export_keys.MORPH_NORMAL] + use_morph_tangents = use_morph_normals and use_tangents and export_settings[gltf2_blender_export_keys.MORPH_TANGENT] + + key_blocks = [] + if blender_mesh.shape_keys and export_settings[gltf2_blender_export_keys.MORPH]: + key_blocks = [ + key_block + for key_block in blender_mesh.shape_keys.key_blocks + if not (key_block == key_block.relative_key or key_block.mute) + ] + + use_materials = export_settings[gltf2_blender_export_keys.MATERIALS] + + # Fetch vert positions and bone data (joint,weights) + + locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings) + if skin: + vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups) + if need_neutral_bone is True: + # Need to create a fake joint at root of armature + # In order to assign not assigned vertices to it + # But for now, this is not yet possible, we need to wait the armature node is created + # Just store this, to be used later + armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature + export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True + + # In Blender there is both per-vert data, like position, and also per-loop + # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert + # data, so we need to split Blender verts up into potentially-multiple glTF + # verts. + # + # First, we'll collect a "dot" for every loop: a struct that stores all the + # attributes at that loop, namely the vertex index (which determines all + # per-vert data), and all the per-loop data like UVs, etc. + # + # Each unique dot will become one unique glTF vert. + + # List all fields the dot struct needs. + dot_fields = [('vertex_index', np.uint32)] + if use_normals: + dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)] + if use_tangents: + dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)] + for uv_i in range(tex_coord_max): + dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)] + # for col_i in range(color_max): + # dot_fields += [ + # ('color%dr' % col_i, np.float32), + # ('color%dg' % col_i, np.float32), + # ('color%db' % col_i, np.float32), + # ('color%da' % col_i, np.float32), + # ] + if use_morph_normals: + for morph_i, _ in enumerate(key_blocks): + dot_fields += [ + ('morph%dnx' % morph_i, np.float32), + ('morph%dny' % morph_i, np.float32), + ('morph%dnz' % morph_i, np.float32), + ] + + dots = np.empty(len(blender_mesh.loops), dtype=np.dtype(dot_fields)) + + vidxs = np.empty(len(blender_mesh.loops)) + blender_mesh.loops.foreach_get('vertex_index', vidxs) + dots['vertex_index'] = vidxs + del vidxs + + if use_normals: + kbs = key_blocks if use_morph_normals else [] + normals, morph_normals = __get_normals( + blender_mesh, kbs, armature, blender_object, export_settings + ) + dots['nx'] = normals[:, 0] + dots['ny'] = normals[:, 1] + dots['nz'] = normals[:, 2] + del normals + for morph_i, ns in enumerate(morph_normals): + dots['morph%dnx' % morph_i] = ns[:, 0] + dots['morph%dny' % morph_i] = ns[:, 1] + dots['morph%dnz' % morph_i] = ns[:, 2] + del morph_normals + + if use_tangents: + tangents = __get_tangents(blender_mesh, armature, blender_object, export_settings) + dots['tx'] = tangents[:, 0] + dots['ty'] = tangents[:, 1] + dots['tz'] = tangents[:, 2] + del tangents + signs = __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings) + dots['tw'] = signs + del signs + + for uv_i in range(tex_coord_max): + uvs = __get_uvs(blender_mesh, uv_i) + dots['uv%dx' % uv_i] = uvs[:, 0] + dots['uv%dy' % uv_i] = uvs[:, 1] + del uvs + + vertex_colors = [__get_colors(blender_mesh, i) for i in range(color_max)] + #for col_i in range(color_max): + # colors = __get_colors(blender_mesh, col_i) + # dots['color%dr' % col_i] = colors[:, 0] + # dots['color%dg' % col_i] = colors[:, 1] + # dots['color%db' % col_i] = colors[:, 2] + # dots['color%da' % col_i] = colors[:, 3] + # del colors + + # Calculate triangles and sort them into primitives. + + blender_mesh.calc_loop_triangles() + loop_indices = np.empty(len(blender_mesh.loop_triangles) * 3, dtype=np.uint32) + blender_mesh.loop_triangles.foreach_get('loops', loop_indices) + + prim_indices = {} # maps material index to TRIANGLES-style indices into dots + + if use_materials == "NONE": # Only for None. For placeholder and export, keep primitives + # Put all vertices into one primitive + prim_indices[-1] = loop_indices + + else: + # Bucket by material index. + + tri_material_idxs = np.empty(len(blender_mesh.loop_triangles), dtype=np.uint32) + blender_mesh.loop_triangles.foreach_get('material_index', tri_material_idxs) + loop_material_idxs = np.repeat(tri_material_idxs, 3) # material index for every loop + unique_material_idxs = np.unique(tri_material_idxs) + del tri_material_idxs + + for material_idx in unique_material_idxs: + prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx] + + # Create all the primitives. + + primitives = [] + + for material_idx, dot_indices in prim_indices.items(): + # Extract just dots used by this primitive, deduplicate them, and + # calculate indices into this deduplicated list. + prim_dots = dots[dot_indices] + prim_dots, indices = np.unique(prim_dots, return_inverse=True) + + if len(prim_dots) == 0: + continue + + # Now just move all the data for prim_dots into attribute arrays + + attributes = {} + + blender_idxs = prim_dots['vertex_index'] + + attributes['POSITION'] = locs[blender_idxs] + for i in range(color_max): + attributes['COLOR_%d' % i] = vertex_colors[i][blender_idxs] + + for morph_i, vs in enumerate(morph_locs): + attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] + + if use_normals: + normals = np.empty((len(prim_dots), 3), dtype=np.float32) + normals[:, 0] = prim_dots['nx'] + normals[:, 1] = prim_dots['ny'] + normals[:, 2] = prim_dots['nz'] + attributes['NORMAL'] = normals + + if use_tangents: + tangents = np.empty((len(prim_dots), 4), dtype=np.float32) + tangents[:, 0] = prim_dots['tx'] + tangents[:, 1] = prim_dots['ty'] + tangents[:, 2] = prim_dots['tz'] + tangents[:, 3] = prim_dots['tw'] + attributes['TANGENT'] = tangents + + if use_morph_normals: + for morph_i, _ in enumerate(key_blocks): + ns = np.empty((len(prim_dots), 3), dtype=np.float32) + ns[:, 0] = prim_dots['morph%dnx' % morph_i] + ns[:, 1] = prim_dots['morph%dny' % morph_i] + ns[:, 2] = prim_dots['morph%dnz' % morph_i] + attributes['MORPH_NORMAL_%d' % morph_i] = ns + + if use_morph_tangents: + attributes['MORPH_TANGENT_%d' % morph_i] = __calc_morph_tangents(normals, ns, tangents) + + for tex_coord_i in range(tex_coord_max): + uvs = np.empty((len(prim_dots), 2), dtype=np.float32) + uvs[:, 0] = prim_dots['uv%dx' % tex_coord_i] + uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i] + attributes['TEXCOORD_%d' % tex_coord_i] = uvs + + #for color_i in range(color_max): + # colors = np.empty((len(prim_dots), 4), dtype=np.float32) + # colors[:, 0] = prim_dots['color%dr' % color_i] + # colors[:, 1] = prim_dots['color%dg' % color_i] + # colors[:, 2] = prim_dots['color%db' % color_i] + # colors[:, 3] = prim_dots['color%da' % color_i] + # attributes['COLOR_%d' % color_i] = colors + + if skin: + joints = [[] for _ in range(num_joint_sets)] + weights = [[] for _ in range(num_joint_sets)] + + for vi in blender_idxs: + bones = vert_bones[vi] + for j in range(0, 4 * num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + attributes['JOINTS_%d' % i] = js + attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': attributes, + 'indices': indices, + 'material': material_idx, + }) + + if export_settings['gltf_loose_edges']: + # Find loose edges + loose_edges = [e for e in blender_mesh.edges if e.is_loose] + blender_idxs = [vi for e in loose_edges for vi in e.vertices] + + if blender_idxs: + # Export one glTF vert per unique Blender vert in a loose edge + blender_idxs = np.array(blender_idxs, dtype=np.uint32) + blender_idxs, indices = np.unique(blender_idxs, return_inverse=True) + + attributes = {} + + attributes['POSITION'] = locs[blender_idxs] + + for morph_i, vs in enumerate(morph_locs): + attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] + + if skin: + joints = [[] for _ in range(num_joint_sets)] + weights = [[] for _ in range(num_joint_sets)] + + for vi in blender_idxs: + bones = vert_bones[vi] + for j in range(0, 4 * num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + attributes['JOINTS_%d' % i] = js + attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': attributes, + 'indices': indices, + 'mode': 1, # LINES + 'material': 0, + }) + + if export_settings['gltf_loose_points']: + # Find loose points + verts_in_edge = set(vi for e in blender_mesh.edges for vi in e.vertices) + blender_idxs = [ + vi for vi, _ in enumerate(blender_mesh.vertices) + if vi not in verts_in_edge + ] + + if blender_idxs: + blender_idxs = np.array(blender_idxs, dtype=np.uint32) + + attributes = {} + + attributes['POSITION'] = locs[blender_idxs] + + for morph_i, vs in enumerate(morph_locs): + attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] + + if skin: + joints = [[] for _ in range(num_joint_sets)] + weights = [[] for _ in range(num_joint_sets)] + + for vi in blender_idxs: + bones = vert_bones[vi] + for j in range(0, 4 * num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + attributes['JOINTS_%d' % i] = js + attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': attributes, + 'mode': 0, # POINTS + 'material': 0, + }) + + print_console('INFO', 'Primitives created: %d' % len(primitives)) + + return primitives + + +def __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings): + locs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32) + source = key_blocks[0].relative_key.data if key_blocks else blender_mesh.vertices + source.foreach_get('co', locs) + locs = locs.reshape(len(blender_mesh.vertices), 3) + + morph_locs = [] + for key_block in key_blocks: + vs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32) + key_block.data.foreach_get('co', vs) + vs = vs.reshape(len(blender_mesh.vertices), 3) + morph_locs.append(vs) + + # Transform for skinning + if armature and blender_object: + # apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world + # loc_transform = armature.matrix_world @ apply_matrix + + loc_transform = blender_object.matrix_world + locs[:] = __apply_mat_to_all(loc_transform, locs) + for vs in morph_locs: + vs[:] = __apply_mat_to_all(loc_transform, vs) + + # glTF stores deltas in morph targets + for vs in morph_locs: + vs -= locs + + if export_settings[gltf2_blender_export_keys.YUP]: + __zup2yup(locs) + for vs in morph_locs: + __zup2yup(vs) + + return locs, morph_locs + + +def __get_normals(blender_mesh, key_blocks, armature, blender_object, export_settings): + """Get normal for each loop.""" + if key_blocks: + normals = key_blocks[0].relative_key.normals_split_get() + normals = np.array(normals, dtype=np.float32) + else: + normals = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32) + blender_mesh.calc_normals_split() + blender_mesh.loops.foreach_get('normal', normals) + + normals = normals.reshape(len(blender_mesh.loops), 3) + + morph_normals = [] + for key_block in key_blocks: + ns = np.array(key_block.normals_split_get(), dtype=np.float32) + ns = ns.reshape(len(blender_mesh.loops), 3) + morph_normals.append(ns) + + # Transform for skinning + if armature and blender_object: + apply_matrix = (armature.matrix_world.inverted_safe() @ blender_object.matrix_world) + apply_matrix = apply_matrix.to_3x3().inverted_safe().transposed() + normal_transform = armature.matrix_world.to_3x3() @ apply_matrix + + normals[:] = __apply_mat_to_all(normal_transform, normals) + __normalize_vecs(normals) + for ns in morph_normals: + ns[:] = __apply_mat_to_all(normal_transform, ns) + __normalize_vecs(ns) + + for ns in [normals, *morph_normals]: + # Replace zero normals with the unit UP vector. + # Seems to happen sometimes with degenerate tris? + is_zero = ~ns.any(axis=1) + ns[is_zero, 2] = 1 + + # glTF stores deltas in morph targets + for ns in morph_normals: + ns -= normals + + if export_settings[gltf2_blender_export_keys.YUP]: + __zup2yup(normals) + for ns in morph_normals: + __zup2yup(ns) + + return normals, morph_normals + + +def __get_tangents(blender_mesh, armature, blender_object, export_settings): + """Get an array of the tangent for each loop.""" + tangents = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32) + blender_mesh.loops.foreach_get('tangent', tangents) + tangents = tangents.reshape(len(blender_mesh.loops), 3) + + # Transform for skinning + if armature and blender_object: + apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world + tangent_transform = apply_matrix.to_quaternion().to_matrix() + tangents = __apply_mat_to_all(tangent_transform, tangents) + __normalize_vecs(tangents) + + if export_settings[gltf2_blender_export_keys.YUP]: + __zup2yup(tangents) + + return tangents + + +def __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings): + signs = np.empty(len(blender_mesh.loops), dtype=np.float32) + blender_mesh.loops.foreach_get('bitangent_sign', signs) + + # Transform for skinning + if armature and blender_object: + # Bitangent signs should flip when handedness changes + # TODO: confirm + apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world + tangent_transform = apply_matrix.to_quaternion().to_matrix() + flipped = tangent_transform.determinant() < 0 + if flipped: + signs *= -1 + + # No change for Zup -> Yup + + return signs + + +def __calc_morph_tangents(normals, morph_normal_deltas, tangents): + # TODO: check if this works + morph_tangent_deltas = np.empty((len(normals), 3), dtype=np.float32) + + for i in range(len(normals)): + n = Vector(normals[i]) + morph_n = n + Vector(morph_normal_deltas[i]) # convert back to non-delta + t = Vector(tangents[i, :3]) + + rotation = morph_n.rotation_difference(n) + + t_morph = Vector(t) + t_morph.rotate(rotation) + morph_tangent_deltas[i] = t_morph - t # back to delta + + return morph_tangent_deltas + + +def __get_uvs(blender_mesh, uv_i): + layer = blender_mesh.uv_layers[uv_i] + uvs = np.empty(len(blender_mesh.loops) * 2, dtype=np.float32) + layer.data.foreach_get('uv', uvs) + uvs = uvs.reshape(len(blender_mesh.loops), 2) + + # Blender UV space -> glTF UV space + # u,v -> u,1-v + uvs[:, 1] *= -1 + uvs[:, 1] += 1 + + return uvs + + +def __get_colors(blender_mesh, color_i): + colors = np.empty(len(blender_mesh.vertices) * 4, dtype=np.float32) + #layer = blender_mesh.vertex_colors[color_i] + blender_mesh.color_attributes[color_i].data.foreach_get('color', colors) + colors = colors.reshape(len(blender_mesh.vertices), 4) + # somehow the colors from blender are > 1.0 sometimes, so clamp here. + colors = np.clip(colors, 0.0, 1.0) + # colors are already linear, no need to switch color space + return colors + +#def __get_colors(blender_mesh, color_i): +# layer = blender_mesh.vertex_colors[color_i] +# colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) +# layer.data.foreach_get('color', colors) +# colors = colors.reshape(len(blender_mesh.loops), 4) +# +# # sRGB -> Linear +# rgb = colors[:, :-1] +# not_small = rgb >= 0.04045 +# small_result = np.where(rgb < 0.0, 0.0, rgb * (1.0 / 12.92)) +# large_result = np.power((rgb + 0.055) * (1.0 / 1.055), 2.4, where=not_small) +# rgb[:] = np.where(not_small, large_result, small_result) +# return colors + + +def __get_bone_data(blender_mesh, skin, blender_vertex_groups): + + need_neutral_bone = False + min_influence = 0.0001 + + joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)} + group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups] + + # List of (joint, weight) pairs for each vert + vert_bones = [] + max_num_influences = 0 + + for vertex in blender_mesh.vertices: + bones = [] + if vertex.groups: + for group_element in vertex.groups: + weight = group_element.weight + if weight <= min_influence: + continue + try: + joint = group_to_joint[group_element.group] + except Exception: + continue + if joint is None: + continue + bones.append((joint, weight)) + bones.sort(key=lambda x: x[1], reverse=True) + if not bones: + # Is not assign to any bone + bones = ((len(skin.joints), 1.0),) # Assign to a joint that will be created later + need_neutral_bone = True + vert_bones.append(bones) + if len(bones) > max_num_influences: + max_num_influences = len(bones) + + # How many joint sets do we need? 1 set = 4 influences + num_joint_sets = (max_num_influences + 3) // 4 + + return vert_bones, num_joint_sets, need_neutral_bone + + +def __zup2yup(array): + # x,y,z -> x,z,-y + array[:, [1,2]] = array[:, [2,1]] # x,z,y + array[:, 2] *= -1 # x,z,-y + + +def __apply_mat_to_all(matrix, vectors): + """Given matrix m and vectors [v1,v2,...], computes [m@v1,m@v2,...]""" + # Linear part + m = matrix.to_3x3() if len(matrix) == 4 else matrix + res = np.matmul(vectors, np.array(m.transposed())) + # Translation part + if len(matrix) == 4: + res += np.array(matrix.translation) + return res + + +def __normalize_vecs(vectors): + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + np.divide(vectors, norms, out=vectors, where=norms != 0) diff --git a/custom_levels/blender/opengoal.py b/custom_levels/blender/opengoal.py new file mode 100644 index 0000000000..dcffcaddfd --- /dev/null +++ b/custom_levels/blender/opengoal.py @@ -0,0 +1,119 @@ +bl_info = { + "name": "OpenGOAL Mesh", + "author": "water111", + "version": (0, 0, 1), + "blender": (2, 83, 0), + "location": "3D View", + "description": "OpenGOAL Mesh tools", + "category": "Development" +} + +import bpy +import colorsys +import bmesh + +from bpy.props import (StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + EnumProperty, + PointerProperty, + ) +from bpy.types import (Panel, + Menu, + Operator, + PropertyGroup, + ) + + +import bpy + +pat_surfaces = [ + ("stone", "stone", "", 0), + ("ice", "ice", "", 1), + ("quicksand", "quicksand", "", 2), + ("waterbottom", "waterbottom", "", 3), + ("tar", "tar", "", 4), + ("sand", "sand", "", 5), + ("wood", "wood", "", 6), + ("grass", "grass", "", 7), + ("pcmetal", "pcmetal", "", 8), + ("snow", "snow", "", 9), + ("deepsnow", "deepsnow", "", 10), + ("hotcoals", "hotcoals", "", 11), + ("lava", "lava", "", 12), + ("crwood", "crwood", "", 13), + ("gravel", "gravel", "", 14), + ("dirt", "dirt", "", 15), + ("metal", "metal", "", 16), + ("straw", "straw", "", 17), + ("tube", "tube", "", 18), + ("swamp", "swamp", "", 19), + ("stopproj", "stopproj", "", 20), + ("rotate", "rotate", "", 21), + ("neutral", "neutral", "", 22), +] + +pat_events = [ + ("none", "none", "", 0), + ("deadly", "deadly", "", 1), + ("endlessfall", "endlessfall", "", 2), + ("burn", "burn", "", 3), + ("deadlyup", "deadlyup", "", 4), + ("burnup", "burnup", "", 5), + ("melt", "melt", "", 6), +] + +def draw_func(self, context): + layout = self.layout + ob = context.object + layout.prop(ob.active_material, "set_collision") + if (ob.active_material.set_collision): + layout.prop(ob.active_material, "ignore") + layout.prop(ob.active_material, "collide_material") + layout.prop(ob.active_material, "collide_event") + layout.prop(ob.active_material, "noedge") + layout.prop(ob.active_material, "noentity") + layout.prop(ob.active_material, "nolineofsight") + layout.prop(ob.active_material, "nocamera") + +def draw_func_ob(self, context): + layout = self.layout + ob = context.object + layout.prop(ob, "set_collision") + if (ob.set_collision): + layout.prop(ob, "ignore") + layout.prop(ob, "collide_material") + layout.prop(ob, "collide_event") + layout.prop(ob, "noedge") + layout.prop(ob, "noentity") + layout.prop(ob, "nolineofsight") + layout.prop(ob, "nocamera") + +def register(): + bpy.types.Material.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties") + bpy.types.Material.ignore = bpy.props.BoolProperty(name="ignore") + bpy.types.Material.noedge = bpy.props.BoolProperty(name="No-Edge") + bpy.types.Material.noentity = bpy.props.BoolProperty(name="No-Entity") + bpy.types.Material.nolineofsight = bpy.props.BoolProperty(name="No-LOS") + bpy.types.Material.nocamera = bpy.props.BoolProperty(name="No-Camera") + bpy.types.Material.collide_material = bpy.props.EnumProperty(items = pat_surfaces, name = "Material") + bpy.types.Material.collide_event = bpy.props.EnumProperty(items = pat_events, name = "Event") + bpy.types.MATERIAL_PT_custom_props.prepend(draw_func) + + bpy.types.Object.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties") + bpy.types.Object.ignore = bpy.props.BoolProperty(name="ignore") + bpy.types.Object.noedge = bpy.props.BoolProperty(name="No-Edge") + bpy.types.Object.noentity = bpy.props.BoolProperty(name="No-Entity") + bpy.types.Object.nolineofsight = bpy.props.BoolProperty(name="No-LOS") + bpy.types.Object.nocamera = bpy.props.BoolProperty(name="No-Camera") + bpy.types.Object.collide_material = bpy.props.EnumProperty(items = pat_surfaces, name = "Material") + bpy.types.Object.collide_event = bpy.props.EnumProperty(items = pat_events, name = "Event") + bpy.types.OBJECT_PT_custom_props.prepend(draw_func_ob) + +def unregister(): + bpy.types.MATERIAL_PT_custom_props.remove(draw_func) + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/game/mips2c/functions/collide_cache.cpp b/game/mips2c/functions/collide_cache.cpp index 50956ebf28..923cf987dd 100644 --- a/game/mips2c/functions/collide_cache.cpp +++ b/game/mips2c/functions/collide_cache.cpp @@ -12,6 +12,8 @@ #include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" using namespace jak1; + +const uint32_t* max_tri_count = nullptr; namespace { u32 vu0_buffer[1024]; // todo, maybe can be 512. u32 vi1 = 0; @@ -507,6 +509,7 @@ u64 execute(void* ctxt) { void link() { cache.fake_scratchpad_data = intern_from_c("*fake-scratchpad-data*").c(); gLinkedFunctionTable.reg("(method 32 collide-cache)", execute, 128); + max_tri_count = intern_from_c("*collide-cache-max-tris*").cast().c(); } } // namespace method_32_collide_cache @@ -542,7 +545,7 @@ u64 execute(void* ctxt) { c->sq(s5, 80, sp); // sq s5, 80(sp) c->sq(gp, 96, sp); // sq gp, 96(sp) // nop // sll r0, r0, 0 - c->addiu(v1, r0, 460); // addiu v1, r0, 460 + c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460 c->lwu(a2, 0, a0); // lwu a2, 0(a0) c->dsubu(t0, v1, a2); // dsubu t0, v1, a2 c->addiu(a3, r0, 64); // addiu a3, r0, 64 @@ -825,7 +828,7 @@ u64 execute(void* ctxt) { c->sq(s5, 80, sp); // sq s5, 80(sp) c->sq(gp, 96, sp); // sq gp, 96(sp) // nop // sll r0, r0, 0 - c->addiu(v1, r0, 460); // addiu v1, r0, 460 + c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460 c->lwu(a2, 0, a0); // lwu a2, 0(a0) c->dsubu(t0, v1, a2); // dsubu t0, v1, a2 c->addiu(a3, r0, 64); // addiu a3, r0, 64 @@ -1120,7 +1123,7 @@ u64 execute(void* ctxt) { c->mov64(v1, v0); // or v1, v0, r0 // nop // sll r0, r0, 0 c->load_symbol(t0, cache.collide_work); // lw t0, *collide-work*(s7) - c->addiu(v1, r0, 460); // addiu v1, r0, 460 + c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460 c->lwu(a0, 0, gp); // lwu a0, 0(gp) c->dsubu(a2, v1, a0); // dsubu a2, v1, a0 c->dsll(a1, a0, 6); // dsll a1, a0, 6 @@ -1629,7 +1632,7 @@ u64 execute(void* ctxt) { // nop // sll r0, r0, 0 // nop // sll r0, r0, 0 c->lwu(a0, 4, gp); // lwu a0, 4(gp) - c->addiu(a1, r0, 460); // addiu a1, r0, 460 + c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460 c->lwu(v1, 0, gp); // lwu v1, 0(gp) c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0 // nop // sll r0, r0, 0 @@ -1875,7 +1878,7 @@ u64 execute(void* ctxt) { // nop // sll r0, r0, 0 // nop // sll r0, r0, 0 c->lwu(a0, 4, gp); // lwu a0, 4(gp) - c->addiu(a1, r0, 460); // addiu a1, r0, 460 + c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460 c->lwu(v1, 0, gp); // lwu v1, 0(gp) c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0 // nop // sll r0, r0, 0 @@ -2123,7 +2126,7 @@ u64 execute(void* ctxt) { c->load_symbol(t2, cache.collide_work); // lw t2, *collide-work*(s7) // nop // sll r0, r0, 0 c->lwu(a0, 4, gp); // lwu a0, 4(gp) - c->addiu(a1, r0, 460); // addiu a1, r0, 460 + c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460 c->lwu(v1, 0, gp); // lwu v1, 0(gp) c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0 // nop // sll r0, r0, 0 diff --git a/goal_src/jak1/engine/collide/collide-cache-h.gc b/goal_src/jak1/engine/collide/collide-cache-h.gc index 25cfb10174..3a58064d12 100644 --- a/goal_src/jak1/engine/collide/collide-cache-h.gc +++ b/goal_src/jak1/engine/collide/collide-cache-h.gc @@ -191,8 +191,23 @@ (define-perm *collide-work* collide-work (new 'global 'collide-work)) +(defglobalconstant BIG_COLLIDE_CACHE_SIZE 5000) (define-perm *collide-cache* collide-cache (new 'global 'collide-cache)) +(#when PC_PORT + ;; disgusting hack to let us overflow the collide cache up to 5k triangles + (malloc 'global (* (size-of collide-cache-tri) BIG_COLLIDE_CACHE_SIZE)) + ;; but start it at the default. + (define *collide-cache-max-tris* 460) +(defmacro activate-big-collide-cache! () + `(set! *collide-cache-max-tris* BIG_COLLIDE_CACHE_SIZE) + ) + + ) + + + + (define-perm *collide-list* collide-list (new 'global 'collide-list)) diff --git a/goal_src/jak1/engine/collide/collide-cache.gc b/goal_src/jak1/engine/collide/collide-cache.gc index 800af98612..819c745214 100644 --- a/goal_src/jak1/engine/collide/collide-cache.gc +++ b/goal_src/jak1/engine/collide/collide-cache.gc @@ -201,7 +201,7 @@ ) (return #f) ) - (when (< 460 (+ (-> obj num-tris) 2)) + (when (< *collide-cache-max-tris* (+ (-> obj num-tris) 2)) (when (not *already-printed-exeeded-max-cache-tris*) (set! *already-printed-exeeded-max-cache-tris* #t) (if (= *cheat-mode* 'debug) diff --git a/goalc/build_level/collide_common.h b/goalc/build_level/collide_common.h index 3ef5146167..a904d02974 100644 --- a/goalc/build_level/collide_common.h +++ b/goalc/build_level/collide_common.h @@ -28,6 +28,7 @@ struct PatSurface { STOPPROJ = 20, ROTATE = 21, NEUTRAL = 22, + MAX_MATERIAL = 23 }; enum class Event { @@ -38,6 +39,7 @@ struct PatSurface { DEADLYUP = 4, BURNUP = 5, MELT = 6, + MAX_EVENT = 7, }; void set_noentity(bool x) { @@ -90,7 +92,7 @@ struct PatSurface { void set_event(Event ev) { val &= ~(0b111111 << 14); - val |= ((u32)ev << 6); + val |= ((u32)ev << 14); } Event get_event() const { return (Event)(0b111111 & (val >> 14)); } diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/gltf_mesh_extract.cpp index 12b77d68e5..455ee13cf6 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/gltf_mesh_extract.cpp @@ -665,6 +665,55 @@ std::optional> subdivide_face_if_needed(CollideFace fac } } +struct PatResult { + bool set = false; + bool ignore = false; + PatSurface pat; +}; + +PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& debug_name) { + PatResult result; + if (!val.IsObject() || !val.Has("set_collision") || !val.Get("set_collision").Get()) { + // unset. + result.set = false; + return result; + } + + result.set = true; + + if (val.Get("ignore").Get()) { + result.ignore = true; + return result; + } + result.ignore = false; + + int mat = val.Get("collide_material").Get(); + ASSERT(mat < (int)PatSurface::Material::MAX_MATERIAL); + result.pat.set_material(PatSurface::Material(mat)); + + int evt = val.Get("collide_event").Get(); + ASSERT(evt < (int)PatSurface::Event::MAX_EVENT); + result.pat.set_event(PatSurface::Event(evt)); + + if (val.Get("nolineofsight").Get()) { + result.pat.set_nolineofsight(true); + } + + if (val.Get("noedge").Get()) { + result.pat.set_noedge(true); + } + + if (val.Get("nocamera").Get()) { + result.pat.set_nocamera(true); + } + + if (val.Get("noentity").Get()) { + result.pat.set_noentity(true); + } + + return result; +} + void extract(const Input& in, CollideOutput& out, const tinygltf::Model& model, @@ -675,14 +724,25 @@ void extract(const Input& in, for (const auto& n : all_nodes) { const auto& node = model.nodes[n.node_idx]; + PatResult mesh_default_collide = custom_props_to_pat(node.extras, node.name); if (node.mesh >= 0) { const auto& mesh = model.meshes[node.mesh]; - if (!mesh.extras.Has("collide")) { - // fmt::print("skip collide: {}\n", mesh.name); - // continue; - } mesh_count++; for (const auto& prim : mesh.primitives) { + // get material + const auto& mat_idx = prim.material; + PatResult pat = mesh_default_collide; + if (mat_idx != -1) { + const auto& mat = model.materials[mat_idx]; + auto mat_pat = custom_props_to_pat(mat.extras, mat.name); + if (mat_pat.set) { + pat = mat_pat; + } + } + + if (pat.set && pat.ignore) { + continue; // skip, no collide here + } prim_count++; // extract index buffer std::vector prim_indices = gltf_index_buffer(model, prim.indices, 0); @@ -728,7 +788,7 @@ void extract(const Input& in, fmt::print("bsphere: {}\n", face.bsphere.to_string_aligned()); } } - + face.pat = pat.pat; out.faces.push_back(face); } }