diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index 1a98c9b..86a4768 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -58,7 +58,7 @@ def psk_export_menu_func(self, context): def psk_import_menu_func(self, context): - self.layout.operator(psk_importer.PskImportOperator.bl_idname, text='Unreal PSK (.psk)') + self.layout.operator(psk_importer.PskImportOperator.bl_idname, text='Unreal PSK (.psk/.pskx)') def psa_export_menu_func(self, context): diff --git a/io_scene_psk_psa/data.py b/io_scene_psk_psa/data.py index 72bb979..1639a01 100644 --- a/io_scene_psk_psa/data.py +++ b/io_scene_psk_psa/data.py @@ -1,4 +1,43 @@ from ctypes import * +from typing import Tuple + + +class Color(Structure): + _fields_ = [ + ('r', c_ubyte), + ('g', c_ubyte), + ('b', c_ubyte), + ('a', c_ubyte), + ] + + def __iter__(self): + yield self.r + yield self.g + yield self.b + yield self.a + + def __eq__(self, other): + return all(map(lambda x: x[0] == x[1], zip(self, other))) + + def __repr__(self): + return repr(tuple(self)) + + def normalized(self) -> Tuple: + return tuple(map(lambda x: x / 255.0, iter(self))) + + +class Vector2(Structure): + _fields_ = [ + ('x', c_float), + ('y', c_float), + ] + + def __iter__(self): + yield self.x + yield self.y + + def __repr__(self): + return repr(tuple(self)) class Vector3(Structure): diff --git a/io_scene_psk_psa/psk/data.py b/io_scene_psk_psa/psk/data.py index ba52a44..68ad7d5 100644 --- a/io_scene_psk_psa/psk/data.py +++ b/io_scene_psk_psa/psk/data.py @@ -41,6 +41,15 @@ class Psk(object): ('smoothing_groups', c_int32) ] + class Face32(Structure): + _pack_ = 1 + _fields_ = [ + ('wedge_indices', c_uint32 * 3), + ('material_index', c_uint8), + ('aux_material_index', c_uint8), + ('smoothing_groups', c_int32) + ] + class Material(Structure): _fields_ = [ ('name', c_char * 64), @@ -71,6 +80,18 @@ class Psk(object): ('bone_index', c_int32), ] + @property + def has_extra_uvs(self): + return len(self.extra_uvs) > 0 + + @property + def has_vertex_colors(self): + return len(self.vertex_colors) + + @property + def has_vertex_normals(self): + return len(self.vertex_normals) + def __init__(self): self.points: List[Vector3] = [] self.wedges: List[Psk.Wedge] = [] @@ -78,3 +99,6 @@ class Psk(object): self.materials: List[Psk.Material] = [] self.weights: List[Psk.Weight] = [] self.bones: List[Psk.Bone] = [] + self.extra_uvs: List[Vector2] = [] + self.vertex_colors: List[Color] = [] + self.vertex_normals: List[Vector3] = [] diff --git a/io_scene_psk_psa/psk/importer.py b/io_scene_psk_psa/psk/importer.py index 81283f8..5d8af92 100644 --- a/io_scene_psk_psa/psk/importer.py +++ b/io_scene_psk_psa/psk/importer.py @@ -120,7 +120,6 @@ class PskImporter(object): bm_face.material_index = face.material_index except ValueError: degenerate_face_indices.add(face_index) - pass if len(degenerate_face_indices) > 0: print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).') @@ -129,7 +128,7 @@ class PskImporter(object): # TEXTURE COORDINATES data_index = 0 - uv_layer = mesh_data.uv_layers.new() + uv_layer = mesh_data.uv_layers.new(name='VTXW0000') for face_index, face in enumerate(psk.faces): if face_index in degenerate_face_indices: continue @@ -138,11 +137,56 @@ class PskImporter(object): uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v data_index += 1 + # EXTRA UVS + if psk.has_extra_uvs: + extra_uv_channel_count = int(len(psk.extra_uvs) / len(psk.wedges)) + wedge_index_offset = 0 + for extra_uv_index in range(extra_uv_channel_count): + data_index = 0 + uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}') + for face_index, face in enumerate(psk.faces): + if face_index in degenerate_face_indices: + continue + for wedge_index in reversed(face.wedge_indices): + u, v = psk.extra_uvs[wedge_index_offset + wedge_index] + uv_layer.data[data_index].uv = u, 1.0 - v + data_index += 1 + wedge_index_offset += len(psk.wedges) + + # VERTEX COLORS + if psk.has_vertex_colors: + vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR') + vertex_colors = [None] * len(psk.points) + ambiguous_vertex_color_point_indices = [] + for wedge_index, wedge in enumerate(psk.wedges): + point_index = wedge.point_index + psk_vertex_color = psk.vertex_colors[wedge_index] + if vertex_colors[point_index] is not None and vertex_colors[point_index] != psk_vertex_color: + ambiguous_vertex_color_point_indices.append(point_index) + vertex_colors[point_index] = psk_vertex_color + + for loop_index, loop in enumerate(mesh_data.loops): + vertex_color = vertex_colors[loop.vertex_index] + if vertex_color is not None: + vertex_color_data.data[loop_index].color = vertex_color.normalized() + else: + vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0 + + if len(ambiguous_vertex_color_point_indices) > 0: + print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.') + + # # VERTEX NORMALS + if psk.has_vertex_normals: + mesh_data.polygons.foreach_set("use_smooth", [True] * len(mesh_data.polygons)) + normals = [] + for vertex_normal in psk.vertex_normals: + normals.append(tuple(vertex_normal)) + mesh_data.normals_split_custom_set_from_vertices(normals) + mesh_data.use_auto_smooth = True + bm.normal_update() bm.free() - # VERTEX WEIGHTS - # Get a list of all bones that have weights associated with them. vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights)) for import_bone in map(lambda x: import_bones[x], sorted(list(vertex_group_bone_indices))): @@ -169,7 +213,7 @@ class PskImportOperator(Operator, ImportHelper): bl_label = 'Export' __doc__ = 'Load a PSK file' filename_ext = '.psk' - filter_glob: StringProperty(default='*.psk', options={'HIDDEN'}) + filter_glob: StringProperty(default='*.psk;*.pskx', options={'HIDDEN'}) filepath: StringProperty( name='File Path', description='File path used for exporting the PSK file', diff --git a/io_scene_psk_psa/psk/reader.py b/io_scene_psk_psa/psk/reader.py index 8f94c36..29eb4ba 100644 --- a/io_scene_psk_psa/psk/reader.py +++ b/io_scene_psk_psa/psk/reader.py @@ -41,6 +41,14 @@ class PskReader(object): PskReader.read_types(fp, Psk.Bone, section, psk.bones) elif section.name == b'RAWWEIGHTS': PskReader.read_types(fp, Psk.Weight, section, psk.weights) + elif section.name == b'FACE3200': + PskReader.read_types(fp, Psk.Face32, section, psk.faces) + elif section.name == b'VERTEXCOLOR': + PskReader.read_types(fp, Color, section, psk.vertex_colors) + elif section.name.startswith(b'EXTRAUVS'): + PskReader.read_types(fp, Vector2, section, psk.extra_uvs) + elif section.name == b'VTXNORMS': + PskReader.read_types(fp, Vector3, section, psk.vertex_normals) else: - raise RuntimeError(f'Unrecognized section "{section.name}"') + raise RuntimeError(f'Unrecognized section "{section.name} at position {15:fp.tell()}"') return psk