5 Commits

Author SHA1 Message Date
07298fd21c feat: add blc params in exporter. fix bme.
- add ballance params section and successive switch in virtools file exporter to allow user manually cancel sector group ensurance which give user a way to export component, not the whole level.
- add mirror matrix detection in bme creation. a mirror matrix will reverse triangle indice order. we need reverse it again when facing mirror matrix. this resolves the issue when applying term 3.
- use mirror, not rotation to create straight floor in bme which give a better looks in game.
2024-04-07 10:35:06 +08:00
3a1a0fb0f6 doc: update document
- add section about ballance related props
2024-04-06 15:43:42 +08:00
3396947115 feat: add ballance map sector field in scene
- add ballance map sector info in scene to indicate the maximum sector count of this map.
- this adding will prevent the bug that the exported ballance map do not have successive sector groups. because original implement will not create sector group if no component in corresponding sector and previous remedy still have bug. and if this happended, ballance will show spaceship in wrong sector. this adding is the final solution of this bug.
- exlarge ballance map sector info when user adding component. the enlarged value will be calculated by user input sector.
- auto enlarge ballance map sector info when importing. this will give user a fluent experience when modifying existing map.
- exporting map will also use ballance map sector info to pre-create successive sector group as term 2 stated.
- move sector name extractor from virtools file exporting module to naming convention module.
2024-04-01 14:39:11 +08:00
6cf2ab895d fix: fix unregister error in virtools group
- fix del attribute error when unregister virtools group.
2024-03-30 16:12:34 +08:00
b039dd8b43 fix: fix grouping and naming error
- fix wood and rail soundeffect grouping error.
- fix checkpoint and resetpoint naming error again (forget to change PC/PR).
2024-02-20 21:52:50 +08:00
18 changed files with 300 additions and 108 deletions

View File

@ -1,7 +1,7 @@
import bpy, mathutils import bpy, mathutils
import math, typing import math, typing
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_ballance_element, PROP_virtools_group from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
#region Param Help Classes #region Param Help Classes
@ -128,6 +128,7 @@ class _GeneralComponentCreator():
""" """
# get element info first # get element info first
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector) ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# create blc element context # create blc element context
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator: with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
# object creation counter # object creation counter
@ -143,6 +144,27 @@ class _GeneralComponentCreator():
# put into created object list # put into created object list
self.__mObjList.append(obj) self.__mObjList.append(obj)
# enlarge scene sector field for non-PS (start point) PE (end point) component
# read from scene and create var for enlarged sector count
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
enlarged_sector: int
# check component type to get enlarged value
match(ele_info.mBasicType):
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
enlarged_sector = comp_sector
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
enlarged_sector = comp_sector + 1
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
enlarged_sector = comp_sector
case _:
# this component is not a sector based component
# so we do not change it (use original value)
enlarged_sector = map_info.mSectorCount
# enlarge it
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
def finish_component(self) -> None: def finish_component(self) -> None:
""" """
Finish up component creation. Finish up component creation.

View File

@ -1,9 +1,9 @@
import bpy import bpy
from bpy_extras.wm_utils.progress_report import ProgressReport from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing, re import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
from .PyBMap import bmap_wrapper as bmap from .PyBMap import bmap_wrapper as bmap
# define global tex save opt blender enum prop helper # define global tex save opt blender enum prop helper
@ -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.", description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(), items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL) default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
) ) # type: ignore
use_compress: bpy.props.BoolProperty( use_compress: bpy.props.BoolProperty(
name="Use Compress", name="Use Compress",
description = "Whether use ZLib to compress result when saving composition.",
default = True, default = True,
) ) # type: ignore
compress_level: bpy.props.IntProperty( compress_level: bpy.props.IntProperty(
name = "Compress Level", name = "Compress Level",
description = "The ZLib compress level used by Virtools Engine when saving composition.", description = "The ZLib compress level used by Virtools Engine when saving composition.",
min = 1, max = 9, min = 1, max = 9,
default = 5, 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 @classmethod
def poll(self, context): 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), _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
self.use_compress, self.use_compress,
self.compress_level, self.compress_level,
self.successive_sector,
objls objls
) )
@ -84,6 +92,14 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
if self.use_compress: if self.use_compress:
box.prop(self, 'compress_level') box.prop(self, 'compress_level')
# 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)
box.prop(self, 'successive_sector')
box.label(text = f'Map Sectors: {map_info.mSectorCount}')
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject] _TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh] _TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial] _TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
@ -95,6 +111,7 @@ def _export_virtools(
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS, texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
use_compress_: bool, use_compress_: bool,
compress_level_: int, compress_level_: int,
successive_sector_: bool,
export_objects: tuple[bpy.types.Object, ...] export_objects: tuple[bpy.types.Object, ...]
) -> None: ) -> None:
@ -114,7 +131,7 @@ def _export_virtools(
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects( obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
writer, progress, export_objects) writer, progress, export_objects)
# export group and 3dobject by prepared 3dobject # 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( mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
writer, progress, obj3d_crets) writer, progress, obj3d_crets)
# export mesh # export mesh
@ -163,6 +180,7 @@ def _prepare_virtools_3dobjects(
def _export_virtools_groups( def _export_virtools_groups(
writer: bmap.BMFileWriter, writer: bmap.BMFileWriter,
progress: ProgressReport, progress: ProgressReport,
successive_sector: bool,
obj3d_crets: tuple[_TObj3dPair, ...] obj3d_crets: tuple[_TObj3dPair, ...]
) -> None: ) -> None:
# create virtools group # create virtools group
@ -170,8 +188,24 @@ def _export_virtools_groups(
# start saving # start saving
progress.enter_substeps(len(obj3d_crets), "Saving Groups") progress.enter_substeps(len(obj3d_crets), "Saving Groups")
# create group exporting helper # create sector group first if user ordered
group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer) # 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.
# Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
# 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.
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: for obj3d, vtobj3d in obj3d_crets:
# open group visitor # open group visitor
@ -180,8 +214,8 @@ def _export_virtools_groups(
# get group or create new group from guard # get group or create new group from guard
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
if vtgroup is None: if vtgroup is None:
# note: no need to set name, guard has set it. vtgroup = writer.create_group()
vtgroup = group_cret_guard.create_group(gp_name) vtgroup.set_name(gp_name)
group_cret_map[gp_name] = vtgroup group_cret_map[gp_name] = vtgroup
# group this object # group this object
@ -471,73 +505,6 @@ def _save_virtools_document(
progress.step() progress.step()
progress.leave_substeps() progress.leave_substeps()
class VirtoolsGroupCreationGuard():
"""
This class 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.
Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
or be ended at a accident sector.
This class hook the operation of Virtools group creation and check all Sector group creation.
Create essential group to make Sector_XX group successive.
Thus all group creation in this module should be passed by this class.
"""
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
@staticmethod
def __get_group_index(group_name: str) -> int | None:
"""
Return the sector index of group name if it is. Otherwise None.
"""
regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name)
if regex_result is not None:
return int(regex_result.group(1))
else:
return None
@staticmethod
def __get_group_name(group_index: int) -> str:
"""
Output Sector group name by given sector index
"""
if group_index == 9:
return 'Sector_9'
else:
return f'Sector_{group_index:0>2d}'
__mWriter: bmap.BMFileWriter
__mSectors: list[bmap.BMGroup]
def __init__(self, assoc_writer: bmap.BMFileWriter):
self.__mWriter = assoc_writer
self.__mSectors = []
def create_group(self, group_name: str) -> bmap.BMGroup:
"""
The hooked group creation function.
Please note the passed group name argument is just for name checking.
This function will set group name for you, not like BMFileWriter.create_group() operated.
"""
# check whether it is sector group
# note: the return sector index is 1 based, not 0
sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name)
# if it is regular group, return normal creation
if sector_idx is None:
gp: bmap.BMGroup = self.__mWriter.create_group()
gp.set_name(group_name)
return gp
# get from sector cahce list
# enlarge sector cache list if it is not fulfilled given sector index
while sector_idx > len(self.__mSectors):
gp: bmap.BMGroup = self.__mWriter.create_group()
self.__mSectors.append(gp)
gp.set_name(self.__get_group_name(len(self.__mSectors)))
# return ordered sector from sector caches
return self.__mSectors[sector_idx - 1]
def register() -> None: def register() -> None:
bpy.utils.register_class(BBP_OT_export_virtools) bpy.utils.register_class(BBP_OT_export_virtools)

View File

@ -2,8 +2,8 @@ import bpy
from bpy_extras.wm_utils.progress_report import ProgressReport from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
from .PyBMap import bmap_wrapper as bmap from .PyBMap import bmap_wrapper as bmap
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams): class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
@ -344,10 +344,12 @@ def _import_virtools_groups(
reader: bmap.BMFileReader, reader: bmap.BMFileReader,
progress: ProgressReport, progress: ProgressReport,
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
) -> dict[bmap.BM3dObject, bpy.types.Object]: ) -> None:
# we need iterate all groups to construct a reversed map # we need iterate all groups to construct a reversed map
# to indicate which groups should this 3dobject be grouped into. # to indicate which groups should this 3dobject be grouped into.
reverse_map: dict[bmap.BM3dObject, set[str]] = {} reverse_map: dict[bmap.BM3dObject, set[str]] = {}
# sector counter to record the maximum sector we have processed.
sector_count: int = 1
# prepare progress # prepare progress
progress.enter_substeps(reader.get_material_count(), "Loading Groups") progress.enter_substeps(reader.get_material_count(), "Loading Groups")
@ -357,6 +359,12 @@ def _import_virtools_groups(
group_name: str | None = vtgroup.get_name() group_name: str | None = vtgroup.get_name()
if group_name is None: continue if group_name is None: continue
# try extracting sector info
potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
if potential_sector_count is not None:
sector_count = max(sector_count, potential_sector_count)
# creating map
for item in vtgroup.get_objects(): for item in vtgroup.get_objects():
# get or create set # get or create set
objgroups: set[str] = reverse_map.get(item, None) objgroups: set[str] = reverse_map.get(item, None)
@ -370,6 +378,11 @@ def _import_virtools_groups(
# step # step
progress.step() progress.step()
# assign to ballance map info according to gotten sector count
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
map_info.mSectorCount = max(map_info.mSectorCount, sector_count)
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
# leave progress # leave progress
progress.leave_substeps() progress.leave_substeps()

View File

@ -0,0 +1,81 @@
import bpy
import typing
from . import UTIL_functions
class RawBallanceMapInfo():
cSectorCount: typing.ClassVar[int] = 1
mSectorCount: int
def __init__(self, **kwargs):
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
def regulate(self):
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)
#region Prop Decl & Getter Setter
class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
sector_count: bpy.props.IntProperty(
name = "Sector",
description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.",
default = 1,
max = 999, min = 1,
soft_max = 8, soft_min = 1,
step = 1
) # type: ignore
def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info:
return scene.ballance_map_info
def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo:
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
rawdata: RawBallanceMapInfo = RawBallanceMapInfo()
rawdata.mSectorCount = props.sector_count
rawdata.regulate()
return rawdata
def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None:
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
props.sector_count = rawdata.mSectorCount
#endregion
class BBP_PT_ballance_map_info(bpy.types.Panel):
"""Show Ballance Map Infos."""
bl_label = "Ballance Map"
bl_idname = "BBP_PT_ballance_map_info"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
@classmethod
def poll(cls, context):
return context.scene is not None
def draw(self, context):
layout: bpy.types.UILayout = self.layout
target: bpy.types.Scene = context.scene
props: BBP_PG_ballance_map_info = get_ballance_map_info(target)
# show map sector count numberbox
layout.prop(props, 'sector_count')
def register() -> None:
# register
bpy.utils.register_class(BBP_PG_ballance_map_info)
bpy.utils.register_class(BBP_PT_ballance_map_info)
# add into scene metadata
bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info)
def unregister() -> None:
# del from scene metadata
del bpy.types.Scene.ballance_map_info
# unregister
bpy.utils.unregister_class(BBP_PG_ballance_map_info)
bpy.utils.unregister_class(BBP_PT_ballance_map_info)

View File

@ -8,7 +8,7 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup):
group_name: bpy.props.StringProperty( group_name: bpy.props.StringProperty(
name = "Group Name", name = "Group Name",
default = "" default = ""
) ) # type: ignore
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty: def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
return obj.virtools_groups return obj.virtools_groups
@ -405,14 +405,14 @@ def register() -> None:
bpy.utils.register_class(BBP_OT_clear_virtools_groups) bpy.utils.register_class(BBP_OT_clear_virtools_groups)
bpy.utils.register_class(BBP_PT_virtools_groups) bpy.utils.register_class(BBP_PT_virtools_groups)
# add into scene metadata # add into object metadata
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group) bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty() bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
def unregister() -> None: def unregister() -> None:
# del from scene metadata # del from object metadata
del bpy.types.Scene.active_virtools_groups del bpy.types.Object.active_virtools_groups
del bpy.types.Scene.virtools_groups del bpy.types.Object.virtools_groups
bpy.utils.unregister_class(BBP_PT_virtools_groups) bpy.utils.unregister_class(BBP_PT_virtools_groups)
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups) bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)

View File

@ -345,6 +345,12 @@ def create_bme_struct(
# get result # get result
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z)) 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 # prepare mesh part data
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient() mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]: def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
@ -370,7 +376,12 @@ def create_bme_struct(
if face_nml_data is None: if face_nml_data is None:
# nml is null, we need compute by ourselves # nml is null, we need compute by ourselves
# get first 3 entries in indices list as the compution ref # 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 # compute it by getting vertices info from prebuild vertices data
# because the normals is computed from transformed vertices # because the normals is computed from transformed vertices
# so no need to correct its by normal transform. # 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() v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
for face_idx in valid_face_idx: for face_idx in valid_face_idx:
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][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 # BME uv do not need any extra process
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params) v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
yield v yield v
@ -424,8 +437,13 @@ def create_bme_struct(
# get face data # get face data
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx] 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 # calc indices count
face_indices: list[int] = face_data[TOKEN_FACES_INDICES]
indices_count: int = len(face_indices) indices_count: int = len(face_indices)
# resize face data to fulfill req # resize face data to fulfill req
while len(f.mIndices) > indices_count: while len(f.mIndices) > indices_count:
@ -499,5 +517,18 @@ def _compute_normals(
corss_mul.normalize() corss_mul.normalize()
return (corss_mul.x, corss_mul.y, corss_mul.z) 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 #endregion

View File

@ -184,6 +184,42 @@ class BallanceObjectInfo():
#endregion #endregion
#region Sector Extractor
_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
def extract_sector_from_name(group_name: str) -> int | None:
"""
A convenient function to extract sector index from given group name.
This function also supports 999 sector plugin.
Not only in this module, but also in outside modules, this function is vary used to extract sector index info.
Function return the index extracted, or None if given group name is not a valid sector group.
The valid sector index is range from 1 to 999 (inclusive)
"""
regex_result = _g_RegexBlcSectorGroup.match(group_name)
if regex_result is not None:
return int(regex_result.group(1))
else:
return None
def build_name_from_sector_index(sector_index: int) -> str:
"""
A convenient function to build Ballance recognizable sector group name.
This function also supports 999 sector plugin.
This function also is used in this module or other modules outside.
Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`.
"""
if sector_index == 9:
return 'Sector_9'
else:
return f'Sector_{sector_index:0>2d}'
#endregion
#region Naming Convention Declaration #region Naming Convention Declaration
_g_BlcNormalComponents: set[str] = set(( _g_BlcNormalComponents: set[str] = set((
@ -227,7 +263,6 @@ _g_BlcWood: set[str] = set((
)) ))
class VirtoolsGroupConvention(): class VirtoolsGroupConvention():
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$') cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$') cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$') cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
@ -255,9 +290,9 @@ class VirtoolsGroupConvention():
counter: int = 0 counter: int = 0
last_matched_sector: int = 0 last_matched_sector: int = 0
for i in gps: for i in gps:
regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i) regex_result: int | None = extract_sector_from_name(i)
if regex_result is not None: if regex_result is not None:
last_matched_sector = int(regex_result.group(1)) last_matched_sector = regex_result
counter += 1 counter += 1
if counter != 1: return None if counter != 1: return None
@ -364,12 +399,12 @@ class VirtoolsGroupConvention():
gp.add_group('Shadow') gp.add_group('Shadow')
case BallanceObjectType.RAIL: case BallanceObjectType.RAIL:
gp.add_group('Phys_FloorRails') gp.add_group('Phys_FloorRails')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_03') gp.add_group('Sound_HitID_03')
gp.add_group('Sound_RollID_03') gp.add_group('Sound_RollID_03')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.STOPPER: case BallanceObjectType.STOPPER:
gp.add_group('Phys_FloorStopper') gp.add_group('Phys_FloorStopper')
@ -377,12 +412,8 @@ class VirtoolsGroupConvention():
# group into component type # group into component type
# use typing.cast() to force linter accept it because None is impossible # use typing.cast() to force linter accept it because None is impossible
gp.add_group(typing.cast(str, info.mComponentType)) gp.add_group(typing.cast(str, info.mComponentType))
# group to sector # group to sector
if info.mSector == 9: gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector)))
gp.add_group('Sector_9')
else:
gp.add_group(f'Sector_{info.mSector:0>2d}')
case _: case _:
if reporter is not None: if reporter is not None:
@ -460,9 +491,9 @@ class YYCToolchainConvention():
case BallanceObjectType.LEVEL_END: case BallanceObjectType.LEVEL_END:
return 'PE_Balloon_01' return 'PE_Balloon_01'
case BallanceObjectType.CHECKPOINT: case BallanceObjectType.CHECKPOINT:
return f'PR_TwoFlames_{info.mSector:0>2d}' return f'PC_TwoFlames_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT: case BallanceObjectType.RESETPOINT:
return f'PC_Resetpoint_{info.mSector:0>2d}' return f'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.DEPTH_CUBE: case BallanceObjectType.DEPTH_CUBE:
return 'DepthCubes_' return 'DepthCubes_'

View File

@ -29,7 +29,8 @@ from . import UTIL_icons_manager
UTIL_icons_manager.register() UTIL_icons_manager.register()
# then load other modules # then load other modules
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
from . import OP_UV_flatten_uv, OP_UV_rail_uv from . import OP_UV_flatten_uv, OP_UV_rail_uv
from . import OP_MTL_fix_material from . import OP_MTL_fix_material
@ -217,6 +218,7 @@ def register() -> None:
PROP_virtools_group.register() PROP_virtools_group.register()
PROP_ballance_element.register() PROP_ballance_element.register()
PROP_bme_material.register() PROP_bme_material.register()
PROP_ballance_map_info.register()
OP_IMPORT_bmfile.register() OP_IMPORT_bmfile.register()
OP_EXPORT_bmfile.register() OP_EXPORT_bmfile.register()
@ -275,6 +277,7 @@ def unregister() -> None:
OP_EXPORT_bmfile.unregister() OP_EXPORT_bmfile.unregister()
OP_IMPORT_bmfile.unregister() OP_IMPORT_bmfile.unregister()
PROP_ballance_map_info.unregister()
PROP_bme_material.unregister() PROP_bme_material.unregister()
PROP_ballance_element.unregister() PROP_ballance_element.unregister()
PROP_virtools_group.unregister() PROP_virtools_group.unregister()

View File

@ -47,7 +47,7 @@
"is_sink": "is_sink", "is_sink": "is_sink",
"is_ribbon": "False" "is_ribbon": "False"
}, },
"transform": "move(length, 5, 0) @ rot(0, 0, 180)" "transform": "move(0, 5, 0) @ scale(1, -1, 1)"
} }
] ]
}, },

View File

@ -0,0 +1,4 @@
# Ballance Properties
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -19,6 +19,7 @@ Therefore, choosing Blender with BBP for mapping is not only choosing freedom an
## Features ## Features
* [Virtools Properties](./virtools-properties.md) * [Virtools Properties](./virtools-properties.md)
* [Ballance Properties](./ballance-properties.md)
* [Import and Export Virtools Document](./import-export-virtools.md) * [Import and Export Virtools Document](./import-export-virtools.md)
* [Group Operation](./group-operations.md) * [Group Operation](./group-operations.md)
* [Legacy Alignment](./legacy-align.md) * [Legacy Alignment](./legacy-align.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,29 @@
# Ballance属性
Ballance属性有别于Virtools属性它是专门为Ballance制图服务的一系列属性。这些属性寄宿于场景在同一场景中制图不会涉及Blender中的场景切换这些属性不会改变。在`Scene`属性面板可以找到Ballance属性相关的面板如下图所示分别是
* `Ballance Elements`面板红色箭头对应Ballance机关
* `BME Materials`面板绿色箭头对应BME材质
* `Ballance Map`面板蓝色箭头对应Ballance地图信息
其中只有Ballance地图信息是你需要重点关注的其它属性在通常情况下不需要关注除非地图中的某些材质或网格出现错误后才需要关注这些属性。
![](../imgs/ballance-properties.png)
## Ballance地图信息
Ballance地图信息目前只有一个选项Sector地图小节数。此属性指示了当前地图的最终期望小节数。这个属性主要是用于解决导出地图Bug的具体Bug内容可参考导入导出Virtools文档章节。
你需要做的唯一一件事就是在导出最终地图前检查此字段是否是你期望的小节数。需要注意的是尽管你可以在创建地图的一开始就设置此字段然而BBP中的其它一些功能可能会修改此字段比如添加机关导入Virtools文档等。例如当你的地图小节指定为3你正在添加一个归属于第4小节的机关那么此值会自动增加到4。同理在导入一个总共4小节的Ballance地图时此值也会增加到4如果先前值小于4的话。这主要是为了用户可以在没有感知到此值的情况下使用此插件尤其是在对某些现有地图进行略微修改时。然而如此操作可能会在某些情况下不能满足用户的需求所以仍建议你在导出前检查此字段。
## Ballance机关
Ballance机关记录了你所有使用BBP添加机关功能添加的机关的网格。在3D文件中通常而言网格占据了最大的数据量因此减少网格的数量即通过在各个相同形状的物体之间共享网格可以大幅减少地图文件所占用的大小。当你使用BBP添加机关相关功能时会首先尝试从这里获取机关的网格如果没有对应网格则加载并记录在这里。
这个面板只供查看不可编辑。当你不小心修改了BBP添加的机关的网格它们本不该被修改想要恢复其原有形状时只需点击`Reset Ballance Elements`即可将列表中的所有机关网格重置为正确状态。
## BME材质
与Ballance机关类似记录了使用BME添加路面时使用的材质。这也是为了可以复用材质而设计的这样就不需要每创建一个物体就创建一套与之相关的材质大大减少了重复材质的数量。
这个面板只供查看不可编辑。当你不小心修改了BME相关的材质它们本不该被修改想要恢复其原有材质时只需点击`Reset BME Materials`即可将列表中的所有材质重置为正确状态。

View File

@ -46,3 +46,10 @@ Global Texture Save Option全局贴图保存选项决定了那些设置了
Use Compress使用压缩属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积且在现代计算机平台上压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后一个额外的Compress Level压缩等级属性将会显示用于指定压缩的级别数值越高压缩率越大文件越小。 Use Compress使用压缩属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积且在现代计算机平台上压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后一个额外的Compress Level压缩等级属性将会显示用于指定压缩的级别数值越高压缩率越大文件越小。
### Ballance参数
Ballance ParamsBallance参数章节包含针对Ballance特有内容对导出过程进行优化的参数。
Successive Sector小节连续是一个解决导出小节组时出现的Bug的选项。由于某些原因如果一个小节中没有任何机关实际上是某小节组中没有归入任何物体导出插件会认为该小节组不存在因而遗漏导出。且由于Ballance对最终小节即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组所以二者叠加会导致Ballance错误地认定地图的小节数从而在错误的小节显示飞船这也就是导出Bug。当勾选此选项后导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组然后再进行导出这样就不会遗漏创建某些小节组飞船也会在正确的小节显示。
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。

View File

@ -16,6 +16,7 @@ Ballance Blender Plugin后文简称BBP是一款关注Ballance自制地图
## 特性 ## 特性
* [Virtools属性](./virtools-properties.md) * [Virtools属性](./virtools-properties.md)
* [Ballance属性](./ballance-properties.md)
* [导入导出Virtools文档](./import-export-virtools.md) * [导入导出Virtools文档](./import-export-virtools.md)
* [按组操作](./group-operations.md) * [按组操作](./group-operations.md)
* [传统对齐](./legacy-align.md) * [传统对齐](./legacy-align.md)

View File

@ -8,7 +8,7 @@
## 使用方法 ## 使用方法
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。 传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
## 面板介绍 ## 面板介绍

View File

@ -52,7 +52,7 @@ BBP插件为所有Blender贴图实际上是Image添加了新的属性
## Virtools网格 ## Virtools网格
BBP插件为所有Blender贴图实际上是Image添加了新的属性称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。 BBP插件为所有Blender网格添加了新的属性称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
![](../imgs/virtools-mesh.png) ![](../imgs/virtools-mesh.png)

View File

@ -11,6 +11,7 @@ nav:
- 'Install Plugin': 'en/install-plugin.md' - 'Install Plugin': 'en/install-plugin.md'
- 'Configure Plugin': 'en/configure-plugin.md' - 'Configure Plugin': 'en/configure-plugin.md'
- 'Virtools Properties': 'en/virtools-properties.md' - 'Virtools Properties': 'en/virtools-properties.md'
- 'Ballance Properties': 'en/ballance-properties.md'
- 'Import and Export Virtools Document': 'en/import-export-virtools.md' - 'Import and Export Virtools Document': 'en/import-export-virtools.md'
- 'Group Operation': 'en/group-operations.md' - 'Group Operation': 'en/group-operations.md'
- 'Legacy Alignment': 'en/legacy-align.md' - 'Legacy Alignment': 'en/legacy-align.md'
@ -27,6 +28,7 @@ nav:
- '安装插件': 'zh-cn/install-plugin.md' - '安装插件': 'zh-cn/install-plugin.md'
- '配置插件': 'zh-cn/configure-plugin.md' - '配置插件': 'zh-cn/configure-plugin.md'
- 'Virtools属性': 'zh-cn/virtools-properties.md' - 'Virtools属性': 'zh-cn/virtools-properties.md'
- 'Ballance属性': 'zh-cn/ballance-properties.md'
- '导入导出Virtools文档': 'zh-cn/import-export-virtools.md' - '导入导出Virtools文档': 'zh-cn/import-export-virtools.md'
- '按组操作': 'zh-cn/group-operations.md' - '按组操作': 'zh-cn/group-operations.md'
- '传统对齐': 'zh-cn/legacy-align.md' - '传统对齐': 'zh-cn/legacy-align.md'