diff --git a/BlenderExtensions/ContextMenu.py b/BlenderExtensions/ContextMenu.py index de9b337..cea9809 100644 --- a/BlenderExtensions/ContextMenu.py +++ b/BlenderExtensions/ContextMenu.py @@ -128,7 +128,7 @@ def execute(self, context): # Create a new sphere for collisions mesh = bpy.data.meshes.new('sphere') bm = bmesh.new() - bmesh.ops.create_icosphere(bm, subdivisions=4, radius=0.5) + bmesh.ops.create_icosphere(bm, subdivisions=4, radius=1) bm.to_mesh(mesh) bm.free() sphere = bpy.data.objects.new('sphere', mesh) @@ -163,7 +163,7 @@ def execute(self, context): mesh = bpy.data.meshes.new('cylinder') bm = bmesh.new() bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=True, - radius1=0.5, radius2=0.5, depth=1.0, + radius1=1, radius2=1, depth=1.0, segments=20, matrix=CONE_ROTATION_MAT) bm.to_mesh(mesh) bm.free() diff --git a/BlenderExtensions/EntityPanels.py b/BlenderExtensions/EntityPanels.py index 891dadb..bfe1d08 100644 --- a/BlenderExtensions/EntityPanels.py +++ b/BlenderExtensions/EntityPanels.py @@ -567,12 +567,18 @@ class NMS_GcInteractionType_Properties(bpy.types.PropertyGroup): class NMS_Vector4f_Properties(bpy.types.PropertyGroup): """ Properties for Vector4f """ - x: FloatProperty(name="x") - y: FloatProperty(name="y") - z: FloatProperty(name="z") - t: FloatProperty(name="t") - - + x: FloatProperty(name="X") + y: FloatProperty(name="Y") + z: FloatProperty(name="Z") + t: FloatProperty(name="W") + +class NMS_Vector4i_Properties(bpy.types.PropertyGroup): + """ Properties for Vector4i """ + x: IntProperty(name="X") + y: IntProperty(name="Y") + z: IntProperty(name="Z") + t: IntProperty(name="W") + class NMS_TkCameraWanderData_Properties(bpy.types.PropertyGroup): """ Properties for TkCameraWanderData """ CamWander: BoolProperty(name="CamWander") @@ -1048,10 +1054,10 @@ def GcInteractionComponentData(self, layout, obj): b2 = b1.box("Camera") b2.row("Distance") b3 = b2.box("Offset") - b3.row("x") - b3.row("y") - b3.row("z") - b3.row("t") + b3.row("X") + b3.row("Y") + b3.row("Z") + b3.row("W") b2.row("Pitch") b2.row("Rotate") b2.row("LightPitch") @@ -1120,10 +1126,10 @@ def TkModelRendererData(self, layout, obj, index=0): b1 = r.box("Camera") b1.row("Distance") b2 = b1.box("Offset") - b2.row("x") - b2.row("y") - b2.row("z") - b2.row("t") + b2.row("X") + b2.row("Y") + b2.row("Z") + b2.row("W") b1.row("Pitch") b1.row("Rotate") b1.row("LightPitch") @@ -1366,6 +1372,7 @@ def get_index(self, obj): NMS_GcSpaceshipComponentData_Properties, NMS_TkCameraWanderData_Properties, NMS_Vector4f_Properties, + NMS_Vector4i_Properties, NMS_TkModelRendererCameraData_Properties, NMS_GcAlienPuzzleMissionOverride_Properties, NMS_TkModelRendererData_Properties, diff --git a/BlenderExtensions/NMSShaderNode.py b/BlenderExtensions/NMSShaderNode.py index d0276c4..f6b59a7 100644 --- a/BlenderExtensions/NMSShaderNode.py +++ b/BlenderExtensions/NMSShaderNode.py @@ -12,8 +12,8 @@ FLAGS = [('_F01_DIFFUSEMAP', 'Diffuse Map', 'Diffuse Map'), ('_F03_NORMALMAP', 'Normal Map', 'Normal Map'), - ('_F21_VERTEXCOLOUR', 'Vertex Colour', 'Vertex Colour'), - ('_F25_ROUGHNESS_MASK', 'Roughness Mask', 'Roughness Mask')] + ('_F21_VERTEXCUSTOM', 'Vertex Custom', 'Vertex Custom'), + ('_F25_MASKS_MAP', 'Masks Map', 'Masks Map')] class NMSShader(bpy.types.NodeCustomGroup): @@ -27,8 +27,8 @@ def operators(self, context): context.space_data.edit_tree list = [('_F01_DIFFUSEMAP', 'Diffuse Map', 'Diffuse Map'), ('_F03_NORMALMAP', 'Normal Map', 'Normal Map'), - ('_F21_VERTEXCOLOUR', 'Vertex Colour', 'Vertex Colour'), - ('_F25_ROUGHNESS_MASK', 'Roughness Mask', 'Roughness Mask')] + ('_F21_VERTEXCUSTOM', 'Vertex Custom', 'Vertex Custom'), + ('_F25_MASKS_MAP', 'Masks Map', 'Masks Map')] return list # Manage the internal nodes to perform the chained operation - clear all @@ -38,7 +38,7 @@ def __nodetree_setup__(self): if self.F01_DIFFUSEMAP_choice: diffuse_texture = self._add_diffuse_texture_choice() - if self.F21_VERTEXCOLOUR_choice: + if self.F21_VERTEXCUSTOM_choice: self._add_vertex_colour_nodes() else: self._remove_vertex_colour_nodes() @@ -111,14 +111,14 @@ def update_nodes(self, context): description='Whether material has a normal map.', default=False, update=update_nodes) - F21_VERTEXCOLOUR_choice: BoolProperty( - name='Has vertex colour data', - description='Whether the material has vertex colour data.', + F21_VERTEXCUSTOM_choice: BoolProperty( + name='Has vertex custom data', + description='Whether the material has vertex custom data.', default=False, update=update_nodes) - F25_ROUGHNESS_MASK_choice: BoolProperty( - name='Has roughness mask', - description='Whether material has a roughness mask.', + F25_MASKS_MAP_choice: BoolProperty( + name='Has masks map', + description='Whether material has a masks map.', default=False, update=update_nodes) @@ -150,9 +150,9 @@ def draw_buttons(self, context, layout): row = layout.row() row.prop(self, 'F03_NORMALMAP_choice', text='Normal Map') row = layout.row() - row.prop(self, 'F21_VERTEXCOLOUR_choice', text='Vertex Colour') + row.prop(self, 'F21_VERTEXCUSTOM_choice', text='Vertex Custom') row = layout.row() - row.prop(self, 'F25_ROUGHNESS_MASK_choice', text='Roughness Mask') + row.prop(self, 'F25_MASKS_MAP_choice', text='Masks Map') # Copy def copy(self, node): diff --git a/ModelExporter/addon_script.py b/ModelExporter/addon_script.py index 65c8aab..1d36bf4 100644 --- a/ModelExporter/addon_script.py +++ b/ModelExporter/addon_script.py @@ -17,11 +17,11 @@ from ModelExporter.animations import process_anims from ModelExporter.export import Export from ModelExporter.Descriptor import Descriptor -from NMS.classes import (TkMaterialData, TkMaterialFlags, - TkVolumeTriggerType, TkMaterialSampler, - TkMaterialUniform, TkRotationComponentData, TkPhysicsComponentData) +from NMS.classes import (TkMaterialData, TkMaterialFlags, TkVolumeTriggerType, + TkMaterialSampler, TkMaterialUniform_Float, TkMaterialUniform_UInt, + TkRotationComponentData, TkPhysicsComponentData) from NMS.classes import TkAnimationComponentData, TkAnimationData -from NMS.classes import List, Vector4f +from NMS.classes import List, Vector4f, Vector4i from NMS.classes import TkAttachmentData from NMS.classes.Object import Object, Model, Mesh, Locator, Reference, Collision, Light, Joint from NMS.LOOKUPS import MATERIALFLAGS @@ -165,7 +165,7 @@ def create_sampler(image, sampler_name: str, texture_dir: str, if op.exists(out_tex_path) and not force_overwrite: print(f'Found existing texture at {out_tex_path}. Using this.') - return TkMaterialSampler(Name=sampler_name, Map=relpath, IsSRGB=True) + return TkMaterialSampler(Name=sampler_name, Map=relpath, IsSRGB=False) # If the textures are packed into the blend file, unpack them. if len(image.packed_files) > 0: @@ -181,7 +181,7 @@ def create_sampler(image, sampler_name: str, texture_dir: str, tex_path = image.filepath_from_user() shutil.copy(tex_path, out_tex_path) if op.exists(out_tex_path): - return TkMaterialSampler(Name=sampler_name, Map=relpath, IsSRGB=True) + return TkMaterialSampler(Name=sampler_name, Map=relpath, IsSRGB=False) else: raise FileNotFoundError(f'Texture not written to {out_tex_path}') @@ -404,26 +404,36 @@ def parse_material(self, ob): "label for the diffuse texture, etc.") # Fetch Uniforms - matuniforms.append(TkMaterialUniform(Name="gMaterialColourVec4", - Values=Vector4f(x=1.0, - y=1.0, - z=1.0, - t=1.0))) - matuniforms.append(TkMaterialUniform(Name="gMaterialParamsVec4", - Values=Vector4f(x=1.0, - y=0.5, - z=1.0, - t=0.0))) - matuniforms.append(TkMaterialUniform(Name="gMaterialSFXVec4", - Values=Vector4f(x=0.0, - y=0.0, - z=0.0, - t=0.0))) - matuniforms.append(TkMaterialUniform(Name="gMaterialSFXColVec4", - Values=Vector4f(x=0.0, - y=0.0, - z=0.0, - t=0.0))) + matuniforms.append(TkMaterialUniform_Float(Name="gMaterialColourVec4", + Values=Vector4f(X=1.000000, + Y=1.000000, + Z=1.000000, + W=1.000000))) + matuniforms.append(TkMaterialUniform_Float(Name="gMaterialParamsVec4", + Values=Vector4f(X=1.000000, + Y=0.500000, + Z=1.000000, + W=0.000000))) + matuniforms.append(TkMaterialUniform_Float(Name="gMaterialParams2Vec4", + Values=Vector4f(X=1.000000, + Y=0.500000, + Z=1.000000, + W=0.000000))) + matuniforms.append(TkMaterialUniform_Float(Name="gMaterialSFXVec4", + Values=Vector4f(X=0.000000, + Y=0.000000, + Z=0.000000, + W=0.000000))) + matuniforms.append(TkMaterialUniform_Float(Name="gMaterialSFXColVec4", + Values=Vector4f(X=0.000000, + Y=0.000000, + Z=0.000000, + W=0.000000))) + matuniforms.append(TkMaterialUniform_UInt(Name="gDynamicFlags", + Values=Vector4i(X=3, + Y=0, + Z=0, + W=0))) if self.settings.get('use_shared_textures'): texture_dir = self.settings.get('shared_texture_folder') @@ -451,10 +461,8 @@ def parse_material(self, ob): # Sort out Mask if mask_image: - # Set _F25_ROUGHNESS_MASK + # Set _F25_MASKS_MAP add_matflags.add(24) - # Set _F39_METALLIC_MASK - add_matflags.add(38) # Add the sampler to the list matsamplers.append(create_sampler( mask_image, "gMasksMap", texture_dir, @@ -686,7 +694,7 @@ def mesh_parser(self, ob, is_coll_mesh: bool = False): uv = uv_layer_data[li].uv uvs[vi] = (uv[0], 1 - uv[1], 0, 1) else: - # Calculate the ev value to write then compare it to what + # Calculate the uv value to write then compare it to what # we have already to see if we need to split the vert. uv = uv_layer_data[li].uv uv = (uv[0], 1 - uv[1], 0, 1) diff --git a/ModelExporter/export.py b/ModelExporter/export.py index a330522..cdde485 100644 --- a/ModelExporter/export.py +++ b/ModelExporter/export.py @@ -22,8 +22,8 @@ import struct from itertools import accumulate # Internal imports -from NMS.classes import TkAttachmentData, TkGeometryData -from NMS.LOOKUPS import SEMANTICS, REV_SEMANTICS, STRIDES +from NMS.classes import TkAttachmentData +from NMS.LOOKUPS import SEMANTICS, REV_SEMANTICS, STRIDES, VERTS from NMS.classes.Object import Model from serialization.NMS_Structures import MBINHeader from serialization.NMS_Structures.Structures import ( @@ -32,10 +32,8 @@ from serialization.NMS_Structures.Structures import ( TkGeometryData as TkGeometryData_new, ) -from serialization.mbincompiler import mbinCompiler from serialization.StreamCompiler import StreamData -from serialization.serializers import (serialize_index_stream, - serialize_vertex_stream) +from serialization.serializers import serialize_vertex_stream from ModelExporter.utils import nmsHash, traverse @@ -98,7 +96,7 @@ def __init__(self, export_directory, scene_directory, scene_name, model: Model, # unique TkMaterialData struct in the set self.materials = set() self.hashes = odict() - self.mesh_names = list() + self.mesh_names: list[str] = list() self.np_index_data = np.array([], dtype=np.uint32) @@ -187,7 +185,7 @@ def __init__(self, export_directory, scene_directory, scene_name, model: Model, self.get_bounds() - # this creates the VertexLayout and SmallVertexLayout properties + # this creates the VertexLayout and PositionVertexLayout properties self.create_vertex_layouts() self.process_nodes() @@ -195,13 +193,6 @@ def __init__(self, export_directory, scene_directory, scene_name, model: Model, # other data. self.mix_streams() - # Assign each of the class objects that contain all of the data their - # data - # if (not self.preserve_node_info - # or (self.preserve_node_info - # and self.export_original_geom_data)): - # self.TkGeometryData = TkGeometryData(**self.GeometryData) - # self.TkGeometryData.make_elements(main=True) self.Model.construct_data() self.TkSceneNodeData = self.Model.get_data() for material in self.materials: @@ -245,14 +236,15 @@ def preprocess_streams(self): 'provided for {} Object'.format(mesh.Name)) self.stream_list = list( - SEMANTICS[x] for x in streams.difference({'Indexes'})) + SEMANTICS[x] for x in streams.difference({'Indexes', 'Vertices'})) self.stream_list.sort() self.element_count = len(self.stream_list) # Create a list to store the offset sizes for each data type offsets = list() for sid in self.stream_list: - offsets.append(STRIDES[sid]) + if sid != VERTS: + offsets.append(STRIDES[sid]) # Now create an ordered dictionary. Each kvp is the sid and the actual # offset as calculated by the sum of all the entries before it. self.offsets = odict() @@ -277,43 +269,45 @@ def serialize_data(self): convert all the provided vertex and index data to bytes to be passed directly to the gstream and geometry file constructors """ - vertex_data = [] vertex_sizes = [] + vertex_pos_sizes = [] index_sizes = [] mesh_datas: list[TkMeshData] = [] for i, name in enumerate(self.mesh_names): + count = len(self.vertex_stream[name]) v_data = serialize_vertex_stream( requires=self.stream_list, - Vertices=self.vertex_stream[name], + count=count, UVs=self.uv_stream[name], Normals=self.n_stream[name], Tangents=self.t_stream[name], Colours=self.c_stream[name] ) + v_pos_data = serialize_vertex_stream( + requires={SEMANTICS["Vertices"]}, + count=count, + Vertices=self.vertex_stream[name], + ) v_len = len(v_data) - vertex_data.append(v_data) vertex_sizes.append(v_len) - # new_indexes = self.index_stream[name] - # # TODO: serialize the same way as they are in the actual data. - # # This will also fail I think if there are indexes > 0xFFFF since it will serialize some as H and - # # some as I - # if max(new_indexes) > 2 ** 16: - # indexes = array('I', new_indexes) - # else: - # indexes = array('H', new_indexes) - # i_data = serialize_index_stream(indexes) + v_pos_len = len(v_pos_data) + vertex_pos_sizes.append(v_pos_len) i_data = self.np_indexes[i] - if self.Indices16Bit: - i_data = i_data.astype(np.uint16) - i_data = i_data.tobytes() + # Depending on how many verts there are, we will need to serialize the indexes differently. + if max(i_data) > 0xFFFF: + i_data = i_data.astype(np.uint32).tobytes() + else: + i_data = i_data.astype(np.uint16).tobytes() i_len = len(i_data) index_sizes.append(i_len) md = TkMeshData( name.upper(), v_data + i_data, + v_pos_data, self.mesh_metadata[name]["hash"], i_len, - v_len + v_len, + v_pos_len, ) mesh_datas.append(md) gstream_data = TkGeometryStreamData(mesh_datas) @@ -321,27 +315,41 @@ def serialize_data(self): with open(self.gstream_fpath, "wb") as f: hdr = MBINHeader() hdr.header_namehash = 0x40025754 - hdr.header_guid = 0x1D6CC846AC06B54C + hdr.header_guid = 0xCCB46895A8B36313 hdr.write(f) gstream_data.write(f) + # This is a list of 3-tuples with the structure (vert_offset, index_offset_vert_pos_offset) offsets = [] # A bit of a hack, but we need the offsets of the index and vert data. We'll use this code to get it # since it works. with open(self.gstream_fpath, "rb") as f: + # Read the number of TkMeshData's serialized. f.seek(0x28, 0) entries = struct.unpack(" Tuple[bytes, bytes]: +def read_gstream(fname: str, info: gstream_info) -> Tuple[bytes, bytes]: """ Read the requested info from the gstream file. Parameters diff --git a/NMS/LOOKUPS.py b/NMS/LOOKUPS.py index 2a38b2e..eb6e402 100644 --- a/NMS/LOOKUPS.py +++ b/NMS/LOOKUPS.py @@ -3,31 +3,21 @@ import numpy as np -MATERIALFLAGS = ['_F01_DIFFUSEMAP', '_F02_SKINNED', '_F03_NORMALMAP', '_F04_', - '_F05_INVERT_ALPHA', '_F06_BRIGHT_EDGE', '_F07_UNLIT', - '_F08_REFLECTIVE', '_F09_TRANSPARENT', '_F10_NORECEIVESHADOW', - '_F11_ALPHACUTOUT', '_F12_BATCHED_BILLBOARD', - '_F13_UVANIMATION', '_F14_UVSCROLL', '_F15_WIND', - '_F16_DIFFUSE2MAP', '_F17_MULTIPLYDIFFUSE2MAP', - '_F18_UVTILES', '_F19_BILLBOARD', '_F20_PARALLAXMAP', - '_F21_VERTEXCOLOUR', '_F22_TRANSPARENT_SCALAR', - '_F23_TRANSLUCENT', '_F24_AOMAP', '_F25_ROUGHNESS_MASK', - '_F26_STRETCHY_PARTICLE', '_F27_VBTANGENT', '_F28_VBSKINNED', - '_F29_VBCOLOUR', '_F30_REFRACTION', '_F31_DISPLACEMENT', - '_F32_REFRACTION_MASK', '_F33_SHELLS', '_F34_GLOW', - '_F35_GLOW_MASK', '_F36_DOUBLESIDED', '_F37_', - '_F38_NO_DEFORM', '_F39_METALLIC_MASK', - '_F40_SUBSURFACE_MASK', '_F41_DETAIL_DIFFUSE', - '_F42_DETAIL_NORMAL', '_F43_NORMAL_TILING', '_F44_IMPOSTER', - '_F45_VERTEX_BLEND', '_F46_BILLBOARD_AT', - '_F47_REFLECTION_PROBE', '_F48_WARPED_DIFFUSE_LIGHTING', - '_F49_DISABLE_AMBIENT', '_F50_DISABLE_POSTPROCESS', - '_F51_DECAL_DIFFUSE', '_F52_DECAL_NORMAL', - '_F53_COLOURISABLE', '_F54_COLOURMASK', '_F55_MULTITEXTURE', - '_F56_MATCH_GROUND', '_F57_DETAIL_OVERLAY', - '_F58_USE_CENTRAL_NORMAL', '_F59_SCREENSPACE_FADE', - '_F60_ACUTE_ANGLE_FADE', '_F61_CLAMP_AMBIENT', - '_F62_DETAIL_ALPHACUTOUT', '_F63_DISSOLVE', '_F64_'] +MATERIALFLAGS = ['_F01_DIFFUSEMAP', '_F02_SKINNED', '_F03_NORMALMAP', '_F04_FEATURESMAP', + '_F05_DEPTH_EFFECT', '_F06_', '_F07_UNLIT', '_F08_', '_F09_REFLECTIVE', + '_F10_', '_F11_ALPHACUTOUT', '_F12_BATCHED_BILLBOARD', '_F13_UV_EFFECT', + '_F14_', '_F15_WIND', '_F16_DIFFUSE2MAP', '_F17_', '_F18_', + '_F19_BILLBOARD', '_F20_PARALLAX', '_F21_VERTEXCUSTOM', + '_F22_OCCLUSION_MAP', '_F23_', '_F24_', '_F25_MASKS_MAP', '_F26_', '_F27_', + '_F28_', '_F29_', '_F30_REFRACTION', '_F31_DISPLACEMENT', + '_F32_REFRACTION_MASK', '_F33_SHELLS', '_F34_', '_F35_', '_F36_DOUBLESIDED', + '_F37_EXPLICIT_MOTION_VECTORS', '_F38_', '_F39_', '_F40_', '_F41_', + '_F42_DETAIL_NORMAL', '_F43_', '_F44_IMPOSTER', '_F45_', '_F46_', + '_F47_REFLECTION_PROBE', '_F48_', '_F49_', '_F50_DISABLE_POSTPROCESS', + '_F51_', '_F52_', '_F53_COLOURISABLE', '_F54_', '_F55_MULTITEXTURE', + '_F56_MATCH_GROUND', '_F57_', '_F58_USE_CENTRAL_NORMAL', + '_F59_BIASED_REACTIVITY', '_F60_', '_F61_', '_F62_', '_F63_DISSOLVE', + '_F64_RESERVED_FLAG_FOR_EARLY_Z_PATCHING_DO_NOT_USE'] # Mesh vertex types VERTS = 0 @@ -39,11 +29,13 @@ BLENDWEIGHT = 6 # Mesh vertex stride sizes -STRIDES = {VERTS: 8, - UVS: 8, - NORMS: 4, - TANGS: 4, - COLOURS: 4} +STRIDES = { + VERTS: 8, + UVS: 8, + NORMS: 4, + TANGS: 4, + COLOURS: 4, +} # Material types DIFFUSE = 'gDiffuseMap' @@ -51,27 +43,33 @@ MASKS = 'gMasksMap' NORMAL = 'gNormalMap' -SEMANTICS = {'Vertices': VERTS, - 'UVs': UVS, - 'Normals': NORMS, - 'Tangents': TANGS, - 'Colours': COLOURS, - 'BlendIndex': BLENDINDEX, - 'BlendWeight': BLENDWEIGHT} +SEMANTICS = { + 'Vertices': VERTS, + 'UVs': UVS, + 'Normals': NORMS, + 'Tangents': TANGS, + 'Colours': COLOURS, + 'BlendIndex': BLENDINDEX, + 'BlendWeight': BLENDWEIGHT, +} -REV_SEMANTICS = {VERTS: 'Vertices', - UVS: 'UVs', - NORMS: 'Normals', - TANGS: 'Tangents', - COLOURS: 'Colours', - BLENDINDEX: 'BlendIndex', - BLENDWEIGHT: 'BlendWeight'} +REV_SEMANTICS = { + VERTS: 'Vertices', + UVS: 'UVs', + NORMS: 'Normals', + TANGS: 'Tangents', + COLOURS: 'Colours', + BLENDINDEX: 'BlendIndex', + BLENDWEIGHT: 'BlendWeight', +} -SERIALIZE_FMT_MAP = {VERTS: 0, - UVS: 0, - NORMS: 1, - TANGS: 1, - COLOURS: 2} +SERIALIZE_FMT_MAP = { + VERTS: 0, + UVS: 0, + NORMS: 1, + TANGS: 1, + COLOURS: 2, +} SERIALIZE_FMT_MAP_NEW = { VERTS: 5131, diff --git a/NMS/classes/Object.py b/NMS/classes/Object.py index 3da93fb..9df5fd9 100644 --- a/NMS/classes/Object.py +++ b/NMS/classes/Object.py @@ -577,10 +577,8 @@ def create_attributes(self, data: dict, ignore_original: bool = False): # Add the LOD info for i, dist in enumerate(self.lod_distances): self.Attributes.append( - TkSceneNodeAttributeData( - Name=f'LODDIST{i + 1}', - Value=dist, - fmt='{0:.6f}')) + TkSceneNodeAttributeData(Name=f'LODDIST{i + 1}', Value=dist) + ) self.Attributes.append( TkSceneNodeAttributeData(Name='NUMLODS', Value=len(self.lod_distances) + 1)) diff --git a/NMS/classes/Quaternion.py b/NMS/classes/Quaternion.py index ba0f30f..3d50212 100644 --- a/NMS/classes/Quaternion.py +++ b/NMS/classes/Quaternion.py @@ -8,17 +8,17 @@ def __init__(self, **kwargs): super(Quaternion, self).__init__() """ Contents of the struct """ - self.data['x'] = kwargs.get('x', 0.0) - self.data['y'] = kwargs.get('y', 0.0) - self.data['z'] = kwargs.get('z', 0.0) - self.data['w'] = kwargs.get('w', 0.0) + self.data['X'] = kwargs.get('X', 0.0) + self.data['Y'] = kwargs.get('Y', 0.0) + self.data['Z'] = kwargs.get('Z', 0.0) + self.data['W'] = kwargs.get('W', 0.0) """ End of the struct contents""" def __str__(self): - return 'Quaternion({0}, {1}, {2}, {3})'.format(self.data['x'], - self.data['y'], - self.data['z'], - self.data['w']) + return 'Quaternion({0}, {1}, {2}, {3})'.format(self.data['X'], + self.data['Y'], + self.data['Z'], + self.data['W']) def __repr__(self): return str(self) diff --git a/NMS/classes/TkAnimationData.py b/NMS/classes/TkAnimationData.py index 311046c..abf39df 100644 --- a/NMS/classes/TkAnimationData.py +++ b/NMS/classes/TkAnimationData.py @@ -26,9 +26,9 @@ def __init__(self, **kwargs): self.data['ActionFrame'] = kwargs.get('ActionFrame', -1) self.data['ControlCreatureSize'] = kwargs.get('ControlCreatureSize', 'AllSizes') - self.data['Additive'] = kwargs.get('Additive', 'False') - self.data['Mirrored'] = kwargs.get('Mirrored', 'False') - self.data['Active'] = kwargs.get('Active', 'True') + self.data['Additive'] = kwargs.get('Additive', 'false') + self.data['Mirrored'] = kwargs.get('Mirrored', 'false') + self.data['Active'] = kwargs.get('Active', 'true') self.data['AdditiveBaseAnim'] = kwargs.get('AdditiveBaseAnim', '') self.data['AdditiveBaseFrame'] = kwargs.get('AdditiveBaseFrame', 0) self.data['GameData'] = kwargs.get('GameData', TkAnimationGameData()) diff --git a/NMS/classes/TkMaterialData.py b/NMS/classes/TkMaterialData.py index a00caf6..5b2ec91 100644 --- a/NMS/classes/TkMaterialData.py +++ b/NMS/classes/TkMaterialData.py @@ -3,8 +3,11 @@ from .Struct import Struct from .List import List from .TkMaterialFlags import TkMaterialFlags -from .TkMaterialUniform import TkMaterialUniform +from .TkMaterialUniform_Float import TkMaterialUniform_Float +from .TkMaterialUniform_UInt import TkMaterialUniform_UInt +from .TkMaterialSampler import TkMaterialSampler from .Vector4f import Vector4f +from .Vector4i import Vector4i from .String import String @@ -14,28 +17,42 @@ def __init__(self, **kwargs): """ Contents of the struct """ self.data['Name'] = String(kwargs.get('Name', ""), 0x80) + self.data['Metamaterial'] = String(kwargs.get('Metamaterial', ""), 0x80) self.data['Class'] = String(kwargs.get('Class', "Opaque"), 0x20) self.data['TransparencyLayerID'] = kwargs.get('TransparencyLayerID', 0) - self.data['CastShadow'] = kwargs.get('CastShadow', "False") - self.data['DisableZTest'] = kwargs.get('DisableZTest', "False") + self.data['CastShadow'] = kwargs.get('CastShadow', "false") + self.data['DisableZTest'] = kwargs.get('DisableZTest', "false") + self.data['CreateFur'] = kwargs.get('CreateFur', "false") + self.data['EnableLodFade'] = kwargs.get('EnableLodFade', "false") self.data['Link'] = String(kwargs.get('Link', ""), 0x80) self.data['Shader'] = String( kwargs.get('Shader', "SHADERS/UBERSHADER.SHADER.BIN"), 0x80) self.data['Flags'] = kwargs.get('Flags', List(TkMaterialFlags())) - self.data['Uniforms'] = kwargs.get( - 'Uniforms', + self.data['FxFlags'] = kwargs.get('FxFlags', None) + self.data['Uniforms_Float'] = kwargs.get( + 'Uniforms_Float', List( - TkMaterialUniform( + TkMaterialUniform_Float( Name="gMaterialColourVec4", - Values=Vector4f(x=1.0, y=1.0, z=1.0, t=1.0)), - TkMaterialUniform( + Values=Vector4f(X=1.000000, Y=1.000000, Z=1.000000, W=1.000000)), + TkMaterialUniform_Float( Name="gMaterialParamsVec4", - Values=Vector4f(x=0.9, y=0.5, z=0.0, t=0.0)), - TkMaterialUniform( + Values=Vector4f(X=0.900000, Y=0.500000, Z=0.000000, W=0.000000)), + TkMaterialUniform_Float( + Name="gMaterialParams2Vec4", + Values=Vector4f(X=0.900000, Y=0.500000, Z=0.000000, W=0.000000)), + TkMaterialUniform_Float( Name="gMaterialSFXVec4", Values=Vector4f()), - TkMaterialUniform( + TkMaterialUniform_Float( Name="gMaterialSFXColVec4", Values=Vector4f()))) - self.data['Samplers'] = kwargs.get('Samplers', None) + self.data['Uniforms_UInt'] = kwargs.get( + 'Uniforms_UInt', + List( + TkMaterialUniform_UInt( + Name="gDynamicFlags", + Values=Vector4i(X=3, Y=0, Z=0, W=0)))) + self.data['Samplers'] = kwargs.get('Samplers', TkMaterialSampler()) + self.data['ShaderMillDataHash'] = kwargs.get('Metamaterial', 0) """ End of the struct contents""" diff --git a/NMS/classes/TkMaterialSampler.py b/NMS/classes/TkMaterialSampler.py index 33c68bf..f6efda8 100644 --- a/NMS/classes/TkMaterialSampler.py +++ b/NMS/classes/TkMaterialSampler.py @@ -16,7 +16,7 @@ def __init__(self, **kwargs): self.data['UseCompression'] = kwargs.get('UseCompression', True) self.data['UseMipMaps'] = kwargs.get('UseMipMaps', True) # True image, False for MASKS and NORMAL - self.data['IsSRGB'] = kwargs.get('IsSRGB', True) + self.data['IsSRGB'] = kwargs.get('IsSRGB', False) self.data['MaterialAlternativeId'] = String(kwargs.get( 'MaterialAlternativeId', ""), 0x10) self.data['TextureAddressMode'] = kwargs.get( diff --git a/NMS/classes/TkMaterialUniform.py b/NMS/classes/TkMaterialUniform_Float.py similarity index 77% rename from NMS/classes/TkMaterialUniform.py rename to NMS/classes/TkMaterialUniform_Float.py index d9a486c..2742942 100644 --- a/NMS/classes/TkMaterialUniform.py +++ b/NMS/classes/TkMaterialUniform_Float.py @@ -1,4 +1,4 @@ -# TkMaterialUniform struct +# TkMaterialUniform_Float struct from .Struct import Struct from .String import String @@ -6,9 +6,9 @@ from .List import List -class TkMaterialUniform(Struct): +class TkMaterialUniform_Float(Struct): def __init__(self, **kwargs): - super(TkMaterialUniform, self).__init__() + super(TkMaterialUniform_Float, self).__init__() """ Contents of the struct """ self.data['Name'] = String(kwargs.get('Name', None), 0x20) diff --git a/NMS/classes/TkMaterialUniform_UInt.py b/NMS/classes/TkMaterialUniform_UInt.py new file mode 100644 index 0000000..4297892 --- /dev/null +++ b/NMS/classes/TkMaterialUniform_UInt.py @@ -0,0 +1,17 @@ +# TkMaterialUniform_UInt struct + +from .Struct import Struct +from .String import String +from .Vector4i import Vector4i +from .List import List + + +class TkMaterialUniform_UInt(Struct): + def __init__(self, **kwargs): + super(TkMaterialUniform_UInt, self).__init__() + + """ Contents of the struct """ + self.data['Name'] = String(kwargs.get('Name', None), 0x20) + self.data['Values'] = kwargs.get('Values', Vector4i()) + self.data['ExtendedValues'] = kwargs.get('ExtendedValues', List()) + """ End of the struct contents""" diff --git a/NMS/classes/Vector4f.py b/NMS/classes/Vector4f.py index 4a141ad..0c18131 100644 --- a/NMS/classes/Vector4f.py +++ b/NMS/classes/Vector4f.py @@ -9,20 +9,20 @@ def __init__(self, **kwargs): super(Vector4f, self).__init__() """ Contents of the struct """ - self.data['x'] = kwargs.get('x', 0.0) - self.data['y'] = kwargs.get('y', 0.0) - self.data['z'] = kwargs.get('z', 0.0) - self.data['t'] = kwargs.get('t', 0.0) + self.data['X'] = kwargs.get('X', 0.0) + self.data['Y'] = kwargs.get('Y', 0.0) + self.data['Z'] = kwargs.get('Z', 0.0) + self.data['W'] = kwargs.get('W', 0.0) """ End of the struct contents""" def __bytes__(self): - return pack('gMaterialParamsVec4.x; # noqa mult_param_x = nodes.new(type="ShaderNodeMath") mult_param_x.operation = 'MULTIPLY' @@ -154,23 +147,12 @@ def create_material_node(mat_path: str, local_root_directory: str): lfRoughness) # If the roughness wasn't ever defined then the default value is 1 # which is what blender has as the default anyway - - # gMaterialParamsVec4.x - # #ifdef _F40_SUBSURFACE_MASK - if 39 in flags: - links.new(principled_BSDF.inputs['Subsurface Weight'], - separate_rgb.outputs['R']) - if 43 in flags: - # lfMetallic = lMasks.b; - links.new(principled_BSDF.inputs['Metallic'], - separate_rgb.outputs['B']) - elif tex_type == NORMAL: # texture _path = realize_path(tex_path, local_root_directory) if _path is not None and op.exists(_path): img = bpy.data.images.load(_path) - img.colorspace_settings.name = 'sRGB' + img.colorspace_settings.name = 'Linear Rec.2020' normal_texture = nodes.new(type='ShaderNodeTexImage') normal_texture.name = normal_texture.label = 'Texture Image - Normal' # noqa normal_texture.image = img @@ -199,25 +181,11 @@ def create_material_node(mat_path: str, local_root_directory: str): links.new(principled_BSDF.inputs['Normal'], normal_map.outputs['Normal']) - if 42 in flags: - # lTexCoordsVec4.xy *= lUniforms.mpCustomPerMesh->gCustomParams01Vec4.z; # noqa - normal_scale = nodes.new(type='ShaderNodeMapping') - normal_scale.location = (-1000, -300) - scale = uniforms['gCustomParams01Vec4'].Values[2] - normal_scale.inputs['Scale'].default_value = Vector((scale, scale, scale)) # noqa - tex_coord = nodes.new(type='ShaderNodeTexCoord') - tex_coord.location = (-1200, -300) - tex_coord.object = bpy.context.active_object - links.new(normal_scale.inputs['Vector'], - tex_coord.outputs['Generated']) - links.new(normal_texture.inputs['Vector'], - normal_scale.outputs['Vector']) - # Apply some final transforms to the data before connecting it to the # Material output node if 20 in flags or 28 in flags: - # #ifdef _F21_VERTEXCOLOUR + # #ifdef _F21_VERTEXCUSTOM # lColourVec4 *= IN( mColourVec4 ); col_attribute = nodes.new(type='ShaderNodeAttribute') col_attribute.attribute_name = 'Col' diff --git a/__init__.py b/__init__.py index 2bd7829..b143674 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "No Man's Sky Development Kit", "author": "gregkwaste, monkeyman192", - "version": (0, 9, "28-pre2"), + "version": (0, 9, 28), "blender": (4, 2, 0), "location": "File > Export/Import", "description": "Create NMS scene structures and export to NMS File format", @@ -19,7 +19,7 @@ # become significantly nicer... import sys import os.path as op -sys.path.append(op.dirname(__file__)) +sys.path = [op.dirname(__file__)] + sys.path # External API operators from .NMSDK import ImportSceneOperator, ImportMeshOperator, ExportSceneOperator diff --git a/serialization/NMS_Structures/NMS_types.py b/serialization/NMS_Structures/NMS_types.py index a62f96a..9e5670e 100644 --- a/serialization/NMS_Structures/NMS_types.py +++ b/serialization/NMS_Structures/NMS_types.py @@ -13,7 +13,23 @@ class VariableSizeString(datatype): + _size = 0x10 _alignment = 8 + _end_padding: int = 0xAAAAAA01 + _pad_with: bytes = b"" + + def __class_getitem__(cls: Type["VariableSizeString"], extra_data: Optional[tuple[int, bytes]]): + if extra_data is not None: + _end_padding, _pad_with = extra_data + else: + _end_padding = 0xAAAAAA01 + _pad_with = b"" + _cls: Type[NMS_list[T]] = types.new_class( + f"VariableSizeString[{extra_data}]", (cls,) + ) + _cls._end_padding = _end_padding + _cls._pad_with = _pad_with + return _cls @classmethod def deserialize(cls, buf: BufferedReader) -> str: @@ -29,9 +45,14 @@ def deserialize(cls, buf: BufferedReader) -> str: @classmethod def serialize(cls, buf: BufferedWriter, value: str): ptr = buf.tell() - buf.write(struct.pack(" list[int]: start = buf.tell() diff --git a/serialization/NMS_Structures/Structures.py b/serialization/NMS_Structures/Structures.py index 09474ac..4fd494a 100644 --- a/serialization/NMS_Structures/Structures.py +++ b/serialization/NMS_Structures/Structures.py @@ -1,10 +1,14 @@ -from typing import Annotated +from typing import Annotated, Type +from io import BufferedWriter, BufferedReader from dataclasses import dataclass +import struct from serialization.cereal_bin.structdata import datatype, Field import serialization.cereal_bin.basic_types as bt -from serialization.NMS_Structures.NMS_types import Vector4f, NMS_list, astring, VariableSizeString, Quaternion_list +from serialization.NMS_Structures.NMS_types import ( + Vector4f, NMS_list, astring, VariableSizeString, Quaternion_list, Vector4i +) # Materials structures @@ -12,16 +16,28 @@ @dataclass class TkMaterialFlags(datatype): - MaterialFlag: Annotated[int, Field(bt.uint32)] + MaterialFlagEnum: Annotated[int, Field(bt.uint32)] @dataclass -class TkMaterialUniform(datatype): +class TkMaterialFxFlags(datatype): + MaterialFxFlagEnum: Annotated[int, Field(bt.uint32)] + + +@dataclass +class TkMaterialUniform_Float(datatype): Values: Annotated[tuple[float, float, float, float], Field(Vector4f)] ExtendedValues: Annotated[list[tuple[float, float, float, float]], Field(NMS_list[Vector4f])] Name: Annotated[str, Field(VariableSizeString)] +@dataclass +class TkMaterialUniform_UInt(datatype): + Values: Annotated[tuple[int, int, int, int], Field(Vector4i)] + ExtendedValues: Annotated[list[tuple[int, int, int, int]], Field(NMS_list[Vector4i])] + Name: Annotated[str, Field(VariableSizeString)] + + @dataclass class TkMaterialSampler(datatype): MaterialAlternativeId: Annotated[str, Field(astring, 0x20)] @@ -39,18 +55,21 @@ class TkMaterialSampler(datatype): @dataclass class TkMaterialData(datatype): Flags: Annotated[list[TkMaterialFlags], Field(datatype=NMS_list[TkMaterialFlags])] + FxFlags: Annotated[list[TkMaterialFxFlags], Field(datatype=NMS_list[TkMaterialFxFlags])] Link: Annotated[str, Field(VariableSizeString)] Metamaterial: Annotated[str, Field(VariableSizeString)] Name: Annotated[str, Field(VariableSizeString)] Samplers: Annotated[list[TkMaterialSampler], Field(datatype=NMS_list[TkMaterialSampler])] Shader: Annotated[str, Field(VariableSizeString)] - Uniforms: Annotated[list[TkMaterialUniform], Field(datatype=NMS_list[TkMaterialUniform])] + Uniforms_Float: Annotated[list[TkMaterialUniform_Float], Field(datatype=NMS_list[TkMaterialUniform_Float])] + Uniforms_UInt: Annotated[list[TkMaterialUniform_UInt], Field(datatype=NMS_list[TkMaterialUniform_UInt])] ShaderMillDataHash: Annotated[int, Field(bt.int64)] TransparencyLayerID: Annotated[int, Field(bt.int32)] Class: Annotated[str, Field(bt.string, 0x20)] CastShadow: Annotated[bool, Field(bt.boolean)] CreateFur: Annotated[bool, Field(bt.boolean)] DisableZTest: Annotated[bool, Field(bt.boolean)] + EnableLodFade: Annotated[bool, Field(bt.boolean)] # Geometry structures @@ -113,6 +132,8 @@ class TkMeshMetaData(datatype): IndexDataSize: Annotated[int, Field(bt.int32)] VertexDataOffset: Annotated[int, Field(bt.int32)] VertexDataSize: Annotated[int, Field(bt.int32)] + VertexPositionDataOffset: Annotated[int, Field(bt.int32)] + VertexPositionDataSize: Annotated[int, Field(bt.int32)] DoubleBufferGeometry: Annotated[bool, Field(bt.boolean)] @@ -121,7 +142,7 @@ class TkMeshMetaData(datatype): @dataclass class TkGeometryData(datatype): - SmallVertexLayout: Annotated[TkVertexLayout, Field(TkVertexLayout)] + PositionVertexLayout: Annotated[TkVertexLayout, Field(TkVertexLayout)] VertexLayout: Annotated[TkVertexLayout, Field(TkVertexLayout)] BoundHullVertEd: Annotated[list[int], Field(NMS_list[bt.int32, 1])] BoundHullVerts: Annotated[list[tuple[float, float, float, float]], Field(NMS_list[Vector4f, 1])] @@ -149,16 +170,18 @@ class TkGeometryData(datatype): @dataclass class TkMeshData(datatype): - IdString: Annotated[str, Field(VariableSizeString)] - MeshDataStream: Annotated[bytearray, Field(NMS_list[bt.uint8])] + IdString: Annotated[str, Field(VariableSizeString[0xFEFE0101, b"\xFE"])] + MeshDataStream: Annotated[bytearray, Field(NMS_list[bt.uint8, 0xFEFE0101])] + MeshPositionDataStream: Annotated[bytearray, Field(NMS_list[bt.uint8, 0xFEFE0101])] Hash: Annotated[int, Field(bt.uint64)] IndexDataSize: Annotated[int, Field(bt.int32)] VertexDataSize: Annotated[int, Field(bt.int32)] + VertexPositionDataSize: Annotated[int, Field(bt.int32)] @dataclass class TkGeometryStreamData(datatype): - StreamDataArray: Annotated[list[TkMeshData], Field(NMS_list[TkMeshData])] + StreamDataArray: Annotated[list[TkMeshData], Field(NMS_list[TkMeshData, 0x101])] # Scene related @@ -224,10 +247,75 @@ class TkAnimMetadata(datatype): Has30HzFrames: Annotated[bool, Field(bt.boolean)] +class NMSTemplate(datatype): + _size = 0x10 + _alignment = 8 + _real_type: datatype + _end_padding: int = 0xEEEEEE01 + + @classmethod + def deserialize(cls, buf: BufferedReader) -> datatype: + start = buf.tell() + offset, namehash, _ = struct.unpack(" T: cls_ = cls.__new__(cls) for name, pytype in cls_.__annotations__.items(): if name.startswith("_"): @@ -175,7 +200,7 @@ def read(cls, buf: BufferedReader): try: setattr(cls_, name, type_._read(buf, meta)) except: - print(f"Error reading {name} ({pytype}) at offset 0x{buf.tell():X}") + print(f"Error reading {cls.__name__}.{name} ({pytype}) at offset 0x{buf.tell():X}") raise return cls_ @@ -186,7 +211,3 @@ class Field: length: Optional[int] = None encoding: Optional[str] = None deferred_loading: bool = False - - -T = TypeVar("T", bound=datatype) -N = TypeVar("N", bound=int) diff --git a/serialization/serializers.py b/serialization/serializers.py index c5e34e6..2efab97 100644 --- a/serialization/serializers.py +++ b/serialization/serializers.py @@ -9,7 +9,7 @@ def serialize_geometry_stream(data): pass -def serialize_vertex_stream(requires: List[str], **kwargs): +def serialize_vertex_stream(requires: List[str], count: int, **kwargs): """ Return a serialized version of the vertex data @@ -21,7 +21,6 @@ def serialize_vertex_stream(requires: List[str], **kwargs): include something. """ data = bytearray() - count = len(kwargs.get('Vertices', list())) if count != 0: for i in range(count): for stream_type in requires: diff --git a/utils/utils.py b/utils/utils.py index 092de51..219d7b5 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -1,7 +1,7 @@ import xml.etree.ElementTree as ET -def mxml_to_dict(fpath: str) -> dict: +def mxml_to_dict(fpath) -> dict: tree = ET.parse(fpath) root = tree.getroot() return element_to_dict(root)