From 7dcb527b223ea395f41584243f8dec0e48bbe14f Mon Sep 17 00:00:00 2001 From: sercero Date: Thu, 8 Feb 2024 17:31:06 -0300 Subject: [PATCH] Add option for exporting manual LOD levels --- io_ogre/config.py | 8 +++++- io_ogre/ogre/mesh.py | 58 +++++++++++++++++++++++++++++++++++++++++--- io_ogre/ui/export.py | 13 +++++----- io_ogre/util.py | 12 ++++----- 4 files changed, 73 insertions(+), 18 deletions(-) diff --git a/io_ogre/config.py b/io_ogre/config.py index 6a00263..b0a38e0 100644 --- a/io_ogre/config.py +++ b/io_ogre/config.py @@ -21,6 +21,12 @@ ('4', 'generate with parity', 'Generate with parity') ] +LOD_METHODS = [ + ('0', 'meshtools', 'Generate LODs using OgreMesh Tools: does LOD by removing edges, which allows only changing the index buffer and re-use the vertex-buffer (storage efficient)'), + ('1', 'blender', 'Generate LODs using Blenders "Decimate" Modifier: does LOD by collapsing vertices, which can result in a visually better LOD, but needs different vertex-buffers per LOD'), + ('2', 'manual', 'Generate LODs by manually crafting the lower LODs: needs different vertex-buffers per LOD') +] + CONFIG_PATH = bpy.utils.user_resource('CONFIG', path='scripts', create=True) CONFIG_FILENAME = 'io_ogre.pickle' CONFIG_FILEPATH = os.path.join(CONFIG_PATH, CONFIG_FILENAME) @@ -75,10 +81,10 @@ 'OPTIMISE_VERTEX_BUFFERS_OPTIONS' : 'puqs', # LOD + 'LOD_GENERATION': '0', 'LOD_LEVELS' : 0, 'LOD_DISTANCE' : 300, 'LOD_PERCENT' : 40, - 'LOD_MESH_TOOLS' : False, # Pose Animation 'SHAPE_ANIMATIONS' : True, diff --git a/io_ogre/ogre/mesh.py b/io_ogre/ogre/mesh.py index 10379f1..0f98ab7 100644 --- a/io_ogre/ogre/mesh.py +++ b/io_ogre/ogre/mesh.py @@ -85,7 +85,8 @@ def dot_mesh(ob, path, force_name=None, ignore_shape_animation=False, normals=Tr overwrite = kwargs.get('overwrite', False) # Don't export hidden or unselected objects unless told to - if not isLOD and ( + if not isLOD and ( + (config.get('LOD_GENERATION') == '2' and "_LOD_" in ob.name) or (not config.get("EXPORT_HIDDEN") and ob not in bpy.context.visible_objects) or (config.get("SELECTED_ONLY") and not ob.select_get()) ): @@ -447,8 +448,57 @@ def dot_mesh(ob, path, force_name=None, ignore_shape_animation=False, normals=Tr logger.info('- Done at %s seconds' % util.timer_diff_str(start)) - # Generate lod levels - if isLOD == False and ob.type == 'MESH' and config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == False: + # Generate LOD levels for manual LOD meshes + if isLOD == False and ob.type == 'MESH' and config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '2': + lod_levels = config.get('LOD_LEVELS') + lod_distance = config.get('LOD_DISTANCE') + + lod_generated = [] + lod_current_distance = lod_distance + + for level in range(lod_levels + 1)[1:]: + lod_ob_name = obj_name + '_LOD_' + str(level) + lod_manual_ob = bpy.context.scene.objects.get(lod_ob_name) + + if lod_manual_ob: + logger.info("- Found LOD Manual object: %s" % lod_ob_name) + lod_generated.append({ 'level': level, 'distance': lod_current_distance, 'lod_manual_ob': lod_manual_ob }) + lod_current_distance += lod_distance + + else: + failure = 'FAILED to manually create LOD levels, manual LOD with name %s NOT FOUND!' % lod_ob_name + Report.warnings.append( failure ) + logger.error( failure ) + break + + # Create lod .mesh files and generate LOD XML to the original .mesh.xml + if len(lod_generated) > 0: + # 'manual' means if the geometry gets loaded from a different file than this LOD list references + doc.start_tag('levelofdetail', { + 'strategy' : 'default', + 'numlevels' : str(len(lod_generated) + 1), # The main mesh is + 1 (kind of weird Ogre logic) + 'manual' : "true" + }) + + logger.info('- Generating: %s LOD meshes. Original: vertices %s, faces: %s' % (len(lod_generated), len(mesh.vertices), len(mesh.loop_triangles))) + for lod in lod_generated: + lod_manual_ob = lod['lod_manual_ob'] + + logger.info("- Writing LOD %s for distance %s, with %s vertices and %s faces" % + (lod['level'], lod['distance'], len(lod_manual_ob.data.vertices), len(lod_manual_ob.data.loop_triangles))) + + dot_mesh(lod_manual_ob, path, lod_manual_ob.data.name, ignore_shape_animation, normals, tangents, isLOD=True) + + # 'value' is the distance this LOD kicks in for the 'Distance' strategy. + doc.leaf_tag('lodmanual', { + 'value' : str(lod['distance']), + 'meshname' : lod_manual_ob.data.name + ".mesh" + }) + + doc.end_tag('levelofdetail') + + # Generate LOD levels automatically using Blenders "Decimate" Modifier + if isLOD == False and ob.type == 'MESH' and config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '1': lod_levels = config.get('LOD_LEVELS') lod_distance = config.get('LOD_DISTANCE') lod_ratio = config.get('LOD_PERCENT') / 100.0 @@ -799,7 +849,7 @@ def replaceInplace(f,searchExp,replaceExp): logger.info('- Created %s.mesh in total time %s seconds' % (obj_name, util.timer_diff_str(start))) # If requested by the user, generate LOD levels / Edge Lists / Vertex buffer optimization through OgreMeshUpgrader - if ((config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True) or + if ((config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0') or (config.get('GENERATE_EDGE_LISTS') == True)): target_mesh_file = os.path.join(path, '%s.mesh' % obj_name ) util.mesh_upgrade_tool(target_mesh_file) diff --git a/io_ogre/ui/export.py b/io_ogre/ui/export.py index 887f097..75b1c9c 100644 --- a/io_ogre/ui/export.py +++ b/io_ogre/ui/export.py @@ -109,7 +109,7 @@ def draw(self, context): "Textures" : ["EX_DDS_MIPS", "EX_FORCE_IMAGE_FORMAT"], "Armature" : ["EX_ARMATURE_ANIMATION", "EX_SHARED_ARMATURE", "EX_ONLY_KEYFRAMES", "EX_ONLY_DEFORMABLE_BONES", "EX_ONLY_KEYFRAMED_BONES", "EX_OGRE_INHERIT_SCALE", "EX_TRIM_BONE_WEIGHTS"], "Mesh" : ["EX_MESH", "EX_MESH_OVERWRITE", "EX_ARRAY", "EX_V1_EXTREMITY_POINTS", "EX_Vx_GENERATE_EDGE_LISTS", "EX_GENERATE_TANGENTS", "EX_Vx_OPTIMISE_ANIMATIONS", "EX_V2_OPTIMISE_VERTEX_BUFFERS", "EX_V2_OPTIMISE_VERTEX_BUFFERS_OPTIONS"], - "LOD" : ["EX_LOD_LEVELS", "EX_LOD_DISTANCE", "EX_LOD_PERCENT", "EX_LOD_MESH_TOOLS"], + "LOD" : ["EX_LOD_GENERATION", "EX_LOD_LEVELS", "EX_LOD_DISTANCE", "EX_LOD_PERCENT"], "Shape Animation" : ["EX_SHAPE_ANIMATIONS", "EX_SHAPE_NORMALS"], "Logging" : ["EX_Vx_ENABLE_LOGGING", "EX_Vx_DEBUG_LOGGING"] } @@ -397,6 +397,11 @@ def execute(self, context): default=config.get('OPTIMISE_VERTEX_BUFFERS_OPTIONS')) = {} # LOD + EX_LOD_GENERATION : EnumProperty( + items=config.LOD_METHODS, + name='LOD Generation Method', + description='Method of generating LOD levels', + default=config.get('LOD_METHODS')) = {} EX_LOD_LEVELS : IntProperty( name="LOD Levels", description="Number of LOD levels", @@ -412,12 +417,6 @@ def execute(self, context): description="LOD percentage reduction", min=0, max=99, default=config.get('LOD_PERCENT')) = {} - EX_LOD_MESH_TOOLS : BoolProperty( - name="Use OgreMesh Tools", - description="""Use OgreMeshUpgrader/OgreMeshTool instead of Blender to generate the mesh LODs. -OgreMeshUpgrader/OgreMeshTool does LOD by removing edges, which allows only changing the index buffer and re-use the vertex-buffer (storage efficient). -Blenders decimate does LOD by collapsing vertices, which can result in a visually better LOD, but needs different vertex-buffers per LOD.""", - default=config.get('LOD_MESH_TOOLS')) = {} # Pose Animation EX_SHAPE_ANIMATIONS : BoolProperty( diff --git a/io_ogre/util.py b/io_ogre/util.py index c7f691b..7c63579 100644 --- a/io_ogre/util.py +++ b/io_ogre/util.py @@ -83,7 +83,7 @@ def mesh_upgrade_tool(infile): if not os.path.exists(infile): logger.warn("Cannot find file mesh file: %s, unable run OgreMeshUpgrader" % filename) - if config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_GENERATION') == '0': Report.warnings.append("OgreMeshUpgrader failed, LODs will not be generated for this mesh: %s" % filename) if config.get('GENERATE_EDGE_LISTS') == True: @@ -107,7 +107,7 @@ def mesh_upgrade_tool(infile): cmd = [exe] - if config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0': cmd.append('-l') cmd.append(str(config.get('LOD_LEVELS'))) @@ -134,7 +134,7 @@ def mesh_upgrade_tool(infile): # Finally, specify input file cmd.append(infile) - if config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0': logger.info("* Generating %s LOD levels for mesh: %s" % (config.get('LOD_LEVELS'), filename)) if config.get('GENERATE_EDGE_LISTS') == True: @@ -153,7 +153,7 @@ def mesh_upgrade_tool(infile): if proc.returncode != 0: logger.warn("OgreMeshUpgrader failed, LODs / Edge Lists / Vertex buffer optimizations will not be generated for this mesh: %s" % filename) - if config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0': Report.warnings.append("OgreMeshUpgrader failed, LODs will not be generated for this mesh: %s" % filename) if config.get('GENERATE_EDGE_LISTS') == True: @@ -163,7 +163,7 @@ def mesh_upgrade_tool(infile): logger.error(error) logger.warn(output) else: - if config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0': logger.info("- Generated %s LOD levels for mesh: %s" % (config.get('LOD_LEVELS'), filename)) if config.get('GENERATE_EDGE_LISTS') == True: @@ -341,7 +341,7 @@ def xml_convert(infile, has_uvs=False): cmd.append('-%s' %config.get('MESH_TOOL_VERSION')) # If requested by the user, generate LOD levels through OgreMeshUpgrader/OgreMeshTool - if config.get('LOD_LEVELS') > 0 and config.get('LOD_MESH_TOOLS') == True: + if config.get('LOD_LEVELS') > 0 and config.get('LOD_GENERATION') == '0': cmd.append('-l') cmd.append(str(config.get('LOD_LEVELS')))