浏览代码

add feature - export particle objects as multimeshinstance to godot (#354)

* add feature particle to multimeshinstance

* fix exporting single object particle will cause error

* fix wrong scale that will scale postion before

* fix for reveiwing

* fix for reveiwing

* pep8

* pep8 line length

* pep8 indent

* mat4 changes and change export particle from active to all

* fix active

* fix wrong

* fix pep8 error

* fix pep8 error ..

* fix pep8 error ...

* Simplify MultiMeshResource key.

* Wrap too-long lines.

Co-authored-by: U-APPLE\celpec <[email protected]>
Co-authored-by: Lu Jiacheng <[email protected]>
epth 4 年之前
父节点
当前提交
a01b0e9ae3

+ 2 - 1
io_scene_godot/__init__.py

@@ -59,7 +59,8 @@ class ExportGodot(bpy.types.Operator, ExportHelper):
             ("CAMERA", "Camera", ""),
             ("LIGHT", "Light", ""),
             ("ARMATURE", "Armature", ""),
-            ("GEOMETRY", "Geometry", "")
+            ("GEOMETRY", "Geometry", ""),
+            ("PARTICLE", "Particle", "")
         ),
         default={
             "EMPTY",

+ 3 - 0
io_scene_godot/converters/__init__.py

@@ -20,6 +20,7 @@ from .mesh import export_mesh_node
 from .physics import export_physics_properties
 from .armature import export_armature_node, export_bone_attachment
 from .animation import export_animation_data
+from .multimesh import export_multimesh_node, has_particle
 
 # TODO: What about Empties which refer to group instances? (in 2.8 Collection
 # Instances)
@@ -38,3 +39,5 @@ BLENDER_TYPE_TO_EXPORTER = {
 BONE_ATTACHMENT_EXPORTER = export_bone_attachment
 
 ANIMATION_DATA_EXPORTER = export_animation_data
+
+MULTIMESH_EXPORTER = export_multimesh_node

+ 167 - 0
io_scene_godot/converters/multimesh.py

@@ -0,0 +1,167 @@
+"""Exports particles as multimesh to Godot"""
+import math
+import bpy
+import mathutils
+
+from ..structures import (
+    NodeTemplate, InternalResource, mat4_to_string)
+from .mesh import ArrayMeshResourceExporter
+
+
+def export_multimesh_node(escn_file, export_settings,
+                          obj, parent_gd_node):
+    """Export blender particle to a MultiMeshInstance"""
+    context = bpy.context
+    dg_eval = context.evaluated_depsgraph_get()
+    obj_eval = context.object.evaluated_get(dg_eval)
+
+    multimeshid_active = None
+    for _ps in obj_eval.particle_systems:
+        # In Blender's particle system params, If "Render - Render As" are
+        # switched to "Collection", there maybe several objects instanced to
+        # one particle, but in Godot one MultiMeshInstance just have one
+        # object to instance, so choose the first object in Blender to display
+        # as the only one object in Godot's MultiMeshInstance's resource.
+        if (_ps.settings.instance_collection and
+                _ps.settings.instance_collection.all_objects[0]):
+            instance_object = _ps.settings.instance_collection.all_objects[0]
+        elif _ps.settings.instance_object:
+            instance_object = _ps.settings.instance_object
+
+        multimeshnode = NodeTemplate(
+            _ps.name, 'MultiMeshInstance', parent_gd_node
+            )
+
+        # Export instance mesh resource first
+        instance_mesh_exporter = ArrayMeshResourceExporter(instance_object)
+
+        mesh_id = instance_mesh_exporter.export_mesh(
+            escn_file, export_settings
+            )
+
+        multimesh_exporter = MultiMeshResourceExporter(obj, mesh_id, _ps)
+
+        multimeshid = multimesh_exporter.export_multimesh(
+            escn_file, export_settings, _ps.name)
+
+        if _ps == obj_eval.particle_systems.active:
+            multimeshid_active = multimeshid
+
+    multimeshnode['multimesh'] = 'SubResource({})'.format(multimeshid_active)
+    multimeshnode['visible'] = obj.visible_get()
+
+    escn_file.add_node(multimeshnode)
+
+    return multimeshnode
+
+
+def has_particle(node):
+    """Returns True if the object has particles"""
+    context = bpy.context
+    dg_eval = context.evaluated_depsgraph_get()
+    obj_eval = context.object.evaluated_get(dg_eval)
+
+    return len(obj_eval.particle_systems) > 0
+
+
+class MultiMeshResourceExporter:
+    """Export a multimesh resource from a blender mesh object"""
+
+    def __init__(self, mesh_object, instance_mesh_id, particle_system):
+        # blender multimesh object
+        self.object = mesh_object
+        self.instance_mesh_id = instance_mesh_id
+        self.particle_system = particle_system
+
+        self.mesh_resource = None
+
+    def export_multimesh(self, escn_file, export_settings, particle_name):
+        """Saves a mesh into the escn file"""
+        converter = MultiMeshConverter(self.particle_system)
+        # Due the missing instance particle support in Godot,
+        # we export one MultiMeshResource from each ParticleSystem.
+        # For now it is safe to use bpy ParticleSystem object as
+        # the hash key.
+        key = self.particle_system
+        # Check if multi-mesh resource exists so we don't bother to export
+        # it twice,
+        multimesh_id = escn_file.get_internal_resource(key)
+        if multimesh_id is not None:
+            return multimesh_id
+
+        multimesh = converter.to_multimesh()
+        if multimesh is not None:
+            self.mesh_resource = MultiMeshResource(particle_name)
+            self.mesh_resource['instance_count'] = '{}'.format(
+                len(self.particle_system.particles))
+            self.mesh_resource['mesh'] = 'SubResource({})'.format(
+                self.instance_mesh_id)
+            self.mesh_resource['transform_array'] = (
+                'PoolVector3Array({})'.format(
+                    converter.to_multimesh())
+                )
+
+            multimesh_id = escn_file.add_internal_resource(
+                self.mesh_resource, key)
+            assert multimesh_id is not None
+
+        return multimesh_id
+
+
+class MultiMeshResource(InternalResource):
+    """Godot MultiMesh resource"""
+
+    def __init__(self, name):
+        super().__init__('MultiMesh', name)
+        self['transform_format'] = 1
+
+        # Change above value in MultiMeshResourceExporter
+        self['instance_count'] = 0
+        self['mesh'] = None
+        self['transform_array'] = None
+
+
+class MultiMeshConverter:
+    """Blender Particles' mat4x4 to
+    Godot MultiMesh resource PoolVector3Array"""
+
+    def __init__(self, particle_system):
+        self.particle_system = particle_system
+
+    def to_multimesh(self):
+        """Evaluates object & converts to final multimesh, ready for export.
+        The multimesh is only temporary."""
+        transform_array = []
+        float32array = ''
+        for _particle in self.particle_system.particles:
+            quat_x = mathutils.Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
+            quat_y = mathutils.Quaternion((0.0, 1.0, 0.0), math.radians(90.0))
+            quat_z = mathutils.Quaternion((0.0, 0.0, 1.0), math.radians(90.0))
+            quat_a = _particle.rotation.copy()
+            quat_a.rotate(quat_x)
+            quat_a.rotate(quat_y)
+            quat_a.rotate(quat_z)
+            quat_a.normalize()
+            rot_tmp = quat_a[1]
+            quat_a[1] = quat_a[3]
+            quat_a[3] = rot_tmp
+
+            rot = quat_a
+            loc = _particle.location - mathutils.Vector((0, 0, 1))
+            scl = _particle.size
+
+            mat_sca_x = mathutils.Matrix.Scale(scl, 4, (1.0, 0.0, 0.0))
+            mat_sca_y = mathutils.Matrix.Scale(scl, 4, (0.0, 1.0, 0.0))
+            mat_sca_z = mathutils.Matrix.Scale(scl, 4, (0.0, 0.0, 1.0))
+
+            mat_rot = rot.to_matrix()
+            mat_trs = mathutils.Matrix.Translation(loc)
+
+            mat = (
+                mat_trs @ mat_rot.to_4x4() @ mat_sca_x @ mat_sca_y @ mat_sca_z
+            )
+
+            mat4 = mat.to_4x4()
+
+            transform_array.append(mat4_to_string(mat4, prefix='', suffix=''))
+        return ','.join(transform_array)

+ 8 - 0
io_scene_godot/export_godot.py

@@ -115,6 +115,14 @@ class GodotExporter:
                 obj,
                 parent_gd_node
             )
+        if ("PARTICLE" in self.config['object_types'] and
+                converters.has_particle(obj)):
+            converters.MULTIMESH_EXPORTER(
+                self.escn_file,
+                self.config,
+                obj,
+                parent_gd_node
+            )
 
         # Perform the export, note that `exported_node.parent` not
         # always the same as `parent_gd_node`, as sometimes, one

+ 2 - 2
io_scene_godot/structures.py

@@ -419,10 +419,10 @@ def gamma_correct(color):
 
 
 # ------------------ Implicit Conversions of Blender Types --------------------
-def mat4_to_string(mtx):
+def mat4_to_string(mtx, prefix='Transform(', suffix=')'):
     """Converts a matrix to a "Transform" string that can be parsed by Godot"""
     mtx = fix_matrix(mtx)
-    array = Array('Transform(')
+    array = Array(prefix, suffix=suffix)
     for row in range(3):
         for col in range(3):
             array.append(mtx[row][col])