diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index e7d45df..cb03da3 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -20,19 +20,26 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool description = "Decide how texture saved if texture is specified as Use Global as its Save Options.", items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(), default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL) - ) + ) # type: ignore use_compress: bpy.props.BoolProperty( name="Use Compress", + description = "Whether use ZLib to compress result when saving composition.", default = True, - ) + ) # type: ignore compress_level: bpy.props.IntProperty( name = "Compress Level", description = "The ZLib compress level used by Virtools Engine when saving composition.", min = 1, max = 9, default = 5, - ) + ) # type: ignore + + successive_sector: bpy.props.BoolProperty( + name="Successive Sector", + description = "Whether order exporter to use document specified sector count to make sure sector is successive.", + default = True, + ) # type: ignore @classmethod def poll(self, context): @@ -59,6 +66,7 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt), self.use_compress, self.compress_level, + self.successive_sector, objls ) @@ -86,8 +94,11 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool # show sector info to notice user layout.separator() + layout.label(text = 'Ballance Params') + box = layout.box() map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) - layout.label(text = f'Map Sectors: {map_info.mSectorCount}') + box.prop(self, 'successive_sector') + box.label(text = f'Map Sectors: {map_info.mSectorCount}') _TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject] _TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh] @@ -100,6 +111,7 @@ def _export_virtools( texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS, use_compress_: bool, compress_level_: int, + successive_sector_: bool, export_objects: tuple[bpy.types.Object, ...] ) -> None: @@ -119,7 +131,7 @@ def _export_virtools( obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects( writer, progress, export_objects) # export group and 3dobject by prepared 3dobject - _export_virtools_groups(writer, progress, obj3d_crets) + _export_virtools_groups(writer, progress, successive_sector_, obj3d_crets) mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects( writer, progress, obj3d_crets) # export mesh @@ -168,6 +180,7 @@ def _prepare_virtools_3dobjects( def _export_virtools_groups( writer: bmap.BMFileWriter, progress: ProgressReport, + successive_sector: bool, obj3d_crets: tuple[_TObj3dPair, ...] ) -> None: # create virtools group @@ -175,7 +188,7 @@ def _export_virtools_groups( # start saving progress.enter_substeps(len(obj3d_crets), "Saving Groups") - # create sector group first + # create sector group first if user ordered # This step is designed for ensure that the created sector group is successive. # # Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have. @@ -183,14 +196,16 @@ def _export_virtools_groups( # or be ended at a accident sector. # # So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly. - map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) - for i in range(map_info.mSectorCount): - gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1) - vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) - if vtgroup is None: - vtgroup = writer.create_group() - vtgroup.set_name(gp_name) - group_cret_map[gp_name] = vtgroup + if successive_sector: + map_info: PROP_ballance_map_info.RawBallanceMapInfo + map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + for i in range(map_info.mSectorCount): + gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1) + vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) + if vtgroup is None: + vtgroup = writer.create_group() + vtgroup.set_name(gp_name) + group_cret_map[gp_name] = vtgroup for obj3d, vtobj3d in obj3d_crets: # open group visitor diff --git a/bbp_ng/UTIL_bme.py b/bbp_ng/UTIL_bme.py index 0d93f3c..c0ccf39 100644 --- a/bbp_ng/UTIL_bme.py +++ b/bbp_ng/UTIL_bme.py @@ -344,6 +344,12 @@ def create_bme_struct( cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv) # get result prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z)) + + # Check whether given transform is mirror matrix + # because mirror matrix will reverse triangle indice order. + # If matrix is mirror matrix, we need reverse it again in following procession, + # including getting uv, calculating normal and providing face data. + mirror_matrix: bool = _is_mirror_matrix(transform) # prepare mesh part data mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient() @@ -370,7 +376,12 @@ def create_bme_struct( if face_nml_data is None: # nml is null, we need compute by ourselves # get first 3 entries in indices list as the compution ref - face_indices_data: list[int] = face_data[TOKEN_FACES_INDICES] + # please note that we may need reverse it + face_indices_data: list[int] + if mirror_matrix: + face_indices_data = face_data[TOKEN_FACES_INDICES][::-1] + else: + face_indices_data = face_data[TOKEN_FACES_INDICES][:] # compute it by getting vertices info from prebuild vertices data # because the normals is computed from transformed vertices # so no need to correct its by normal transform. @@ -399,7 +410,9 @@ def create_bme_struct( v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2() for face_idx in valid_face_idx: face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx] - for i in range(len(face_data[TOKEN_FACES_INDICES])): + # iterate uv list considering mirror matrix + indices_count: int = len(face_data[TOKEN_FACES_INDICES]) + for i in (range(indices_count)[::-1] if mirror_matrix else range(indices_count)): # BME uv do not need any extra process v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params) yield v @@ -424,8 +437,13 @@ def create_bme_struct( # get face data face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx] + # get face indices considering the mirror matrix + face_indices: list[int] + if mirror_matrix: + face_indices = face_data[TOKEN_FACES_INDICES][::-1] + else: + face_indices = face_data[TOKEN_FACES_INDICES][:] # calc indices count - face_indices: list[int] = face_data[TOKEN_FACES_INDICES] indices_count: int = len(face_indices) # resize face data to fulfill req while len(f.mIndices) > indices_count: @@ -499,5 +517,18 @@ def _compute_normals( corss_mul.normalize() return (corss_mul.x, corss_mul.y, corss_mul.z) +def _is_mirror_matrix(mat: mathutils.Matrix) -> bool: + """ + Reflection matrix (aka. mirror matrix) is a special scaling matrix. + In this matrix, 1 or 3 scaling factor is minus number. + + Mirror matrix will cause the inverse of triangle indice order. + So we need detect it and re-reverse when creating bm struct. + This function can detect whether given matrix is mirror matrix. + + Reference: https://zhuanlan.zhihu.com/p/96717729 + """ + return mat.is_negative + #return mat.to_3x3().determinant() < 0 #endregion diff --git a/bbp_ng/raw_jsons/streets.json b/bbp_ng/raw_jsons/streets.json index 2d062e0..edc79d4 100644 Binary files a/bbp_ng/raw_jsons/streets.json and b/bbp_ng/raw_jsons/streets.json differ