multimesh.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """Exports particles as multimesh to Godot"""
  2. import math
  3. import bpy
  4. import mathutils
  5. from ..structures import (
  6. NodeTemplate, InternalResource, mat4_to_string)
  7. from .mesh import ArrayMeshResourceExporter
  8. def export_multimesh_node(escn_file, export_settings,
  9. obj, parent_gd_node):
  10. """Export blender particle to a MultiMeshInstance"""
  11. context = bpy.context
  12. dg_eval = context.evaluated_depsgraph_get()
  13. obj_eval = context.object.evaluated_get(dg_eval)
  14. multimeshid_active = None
  15. for _ps in obj_eval.particle_systems:
  16. # In Blender's particle system params, If "Render - Render As" are
  17. # switched to "Collection", there maybe several objects instanced to
  18. # one particle, but in Godot one MultiMeshInstance just have one
  19. # object to instance, so choose the first object in Blender to display
  20. # as the only one object in Godot's MultiMeshInstance's resource.
  21. if (_ps.settings.instance_collection and
  22. _ps.settings.instance_collection.all_objects[0]):
  23. instance_object = _ps.settings.instance_collection.all_objects[0]
  24. elif _ps.settings.instance_object:
  25. instance_object = _ps.settings.instance_object
  26. multimeshnode = NodeTemplate(
  27. _ps.name, 'MultiMeshInstance', parent_gd_node
  28. )
  29. # Export instance mesh resource first
  30. instance_mesh_exporter = ArrayMeshResourceExporter(instance_object)
  31. mesh_id = instance_mesh_exporter.export_mesh(
  32. escn_file, export_settings
  33. )
  34. multimesh_exporter = MultiMeshResourceExporter(obj, mesh_id, _ps)
  35. multimeshid = multimesh_exporter.export_multimesh(
  36. escn_file, export_settings, _ps.name)
  37. if _ps == obj_eval.particle_systems.active:
  38. multimeshid_active = multimeshid
  39. multimeshnode['multimesh'] = 'SubResource({})'.format(multimeshid_active)
  40. multimeshnode['visible'] = obj.visible_get()
  41. escn_file.add_node(multimeshnode)
  42. return multimeshnode
  43. def has_particle(node):
  44. """Returns True if the object has particles"""
  45. context = bpy.context
  46. dg_eval = context.evaluated_depsgraph_get()
  47. obj_eval = context.object.evaluated_get(dg_eval)
  48. return len(obj_eval.particle_systems) > 0
  49. class MultiMeshResourceExporter:
  50. """Export a multimesh resource from a blender mesh object"""
  51. def __init__(self, mesh_object, instance_mesh_id, particle_system):
  52. # blender multimesh object
  53. self.object = mesh_object
  54. self.instance_mesh_id = instance_mesh_id
  55. self.particle_system = particle_system
  56. self.mesh_resource = None
  57. def export_multimesh(self, escn_file, export_settings, particle_name):
  58. """Saves a mesh into the escn file"""
  59. converter = MultiMeshConverter(self.particle_system)
  60. # Due the missing instance particle support in Godot,
  61. # we export one MultiMeshResource from each ParticleSystem.
  62. # For now it is safe to use bpy ParticleSystem object as
  63. # the hash key.
  64. key = self.particle_system
  65. # Check if multi-mesh resource exists so we don't bother to export
  66. # it twice,
  67. multimesh_id = escn_file.get_internal_resource(key)
  68. if multimesh_id is not None:
  69. return multimesh_id
  70. multimesh = converter.to_multimesh()
  71. if multimesh is not None:
  72. self.mesh_resource = MultiMeshResource(particle_name)
  73. self.mesh_resource['instance_count'] = '{}'.format(
  74. len(self.particle_system.particles))
  75. self.mesh_resource['mesh'] = 'SubResource({})'.format(
  76. self.instance_mesh_id)
  77. self.mesh_resource['transform_array'] = (
  78. 'PoolVector3Array({})'.format(
  79. converter.to_multimesh())
  80. )
  81. multimesh_id = escn_file.add_internal_resource(
  82. self.mesh_resource, key)
  83. assert multimesh_id is not None
  84. return multimesh_id
  85. class MultiMeshResource(InternalResource):
  86. """Godot MultiMesh resource"""
  87. def __init__(self, name):
  88. super().__init__('MultiMesh', name)
  89. self['transform_format'] = 1
  90. # Change above value in MultiMeshResourceExporter
  91. self['instance_count'] = 0
  92. self['mesh'] = None
  93. self['transform_array'] = None
  94. class MultiMeshConverter:
  95. """Blender Particles' mat4x4 to
  96. Godot MultiMesh resource PoolVector3Array"""
  97. def __init__(self, particle_system):
  98. self.particle_system = particle_system
  99. def to_multimesh(self):
  100. """Evaluates object & converts to final multimesh, ready for export.
  101. The multimesh is only temporary."""
  102. transform_array = []
  103. float32array = ''
  104. for _particle in self.particle_system.particles:
  105. quat_x = mathutils.Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
  106. quat_y = mathutils.Quaternion((0.0, 1.0, 0.0), math.radians(90.0))
  107. quat_z = mathutils.Quaternion((0.0, 0.0, 1.0), math.radians(90.0))
  108. quat_a = _particle.rotation.copy()
  109. quat_a.rotate(quat_x)
  110. quat_a.rotate(quat_y)
  111. quat_a.rotate(quat_z)
  112. quat_a.normalize()
  113. rot_tmp = quat_a[1]
  114. quat_a[1] = quat_a[3]
  115. quat_a[3] = rot_tmp
  116. rot = quat_a
  117. loc = _particle.location - mathutils.Vector((0, 0, 1))
  118. scl = _particle.size
  119. mat_sca_x = mathutils.Matrix.Scale(scl, 4, (1.0, 0.0, 0.0))
  120. mat_sca_y = mathutils.Matrix.Scale(scl, 4, (0.0, 1.0, 0.0))
  121. mat_sca_z = mathutils.Matrix.Scale(scl, 4, (0.0, 0.0, 1.0))
  122. mat_rot = rot.to_matrix()
  123. mat_trs = mathutils.Matrix.Translation(loc)
  124. mat = (
  125. mat_trs @ mat_rot.to_4x4() @ mat_sca_x @ mat_sca_y @ mat_sca_z
  126. )
  127. mat4 = mat.to_4x4()
  128. transform_array.append(mat4_to_string(mat4, prefix='', suffix=''))
  129. return ','.join(transform_array)