physics.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. """
  2. The physics converter is a little special as it runs before the blender
  3. object is exported. In blender, the object owns the physics. In Godot, the
  4. physics owns the object.
  5. """
  6. import logging
  7. import bpy
  8. import mathutils
  9. import bmesh
  10. from ..structures import NodeTemplate, InternalResource, Array, _AXIS_CORRECT
  11. PHYSICS_TYPES = {'KinematicBody', 'RigidBody', 'StaticBody'}
  12. def has_physics(node):
  13. """Returns True if the object has physics enabled"""
  14. return node.rigid_body is not None
  15. def is_physics_root(node):
  16. """Checks to see if this object is the root of the physics tree. This is
  17. True if none of the parents of the object have physics."""
  18. return get_physics_root(node) is None
  19. def get_physics_root(node):
  20. """ Check upstream for other rigid bodies (to allow compound shapes).
  21. Returns the upstream-most rigid body and how many nodes there are between
  22. this node and the parent """
  23. parent_rbd = None
  24. current_node = node
  25. while current_node.parent is not None:
  26. if current_node.parent.rigid_body is not None:
  27. parent_rbd = current_node.parent
  28. current_node = current_node.parent
  29. return parent_rbd
  30. def get_extents(node):
  31. """Returns X, Y and Z total height"""
  32. raw = node.bound_box
  33. vecs = [mathutils.Vector(v) for v in raw]
  34. mins = vecs[0].copy()
  35. maxs = vecs[0].copy()
  36. for vec in vecs:
  37. mins.x = min(vec.x, mins.x)
  38. mins.y = min(vec.y, mins.y)
  39. mins.z = min(vec.z, mins.z)
  40. maxs.x = max(vec.x, maxs.x)
  41. maxs.y = max(vec.y, maxs.y)
  42. maxs.z = max(vec.z, maxs.z)
  43. return maxs - mins
  44. def export_collision_shape(escn_file, export_settings, node, parent_gd_node,
  45. parent_override=None):
  46. """Exports the collision primitives/geometry"""
  47. col_name = node.name + 'Collision'
  48. col_node = NodeTemplate(col_name, "CollisionShape", parent_gd_node)
  49. if parent_override is None:
  50. col_node['transform'] = mathutils.Matrix.Identity(4)
  51. else:
  52. parent_to_world = parent_override.matrix_world.inverted_safe()
  53. col_node['transform'] = parent_to_world @ node.matrix_world
  54. col_node['transform'] = col_node['transform'] @ _AXIS_CORRECT
  55. rbd = node.rigid_body
  56. shape_id = None
  57. bounds = get_extents(node)
  58. if rbd.collision_shape == "BOX":
  59. col_shape = InternalResource("BoxShape", col_name)
  60. col_shape['extents'] = mathutils.Vector(bounds/2)
  61. shape_id = escn_file.add_internal_resource(col_shape, rbd)
  62. elif rbd.collision_shape == "SPHERE":
  63. col_shape = InternalResource("SphereShape", col_name)
  64. col_shape['radius'] = max(list(bounds))/2
  65. shape_id = escn_file.add_internal_resource(col_shape, rbd)
  66. elif rbd.collision_shape == "CAPSULE":
  67. col_shape = InternalResource("CapsuleShape", col_name)
  68. col_shape['radius'] = max(bounds.x, bounds.y) / 2
  69. col_shape['height'] = bounds.z - col_shape['radius'] * 2
  70. shape_id = escn_file.add_internal_resource(col_shape, rbd)
  71. elif rbd.collision_shape == "CONVEX_HULL":
  72. col_shape, shape_id = generate_convex_mesh_array(
  73. escn_file, export_settings,
  74. node
  75. )
  76. elif rbd.collision_shape == "MESH":
  77. col_shape, shape_id = generate_triangle_mesh_array(
  78. escn_file, export_settings,
  79. node
  80. )
  81. else:
  82. logging.warning("Unable to export physics shape for %s", node.name)
  83. if shape_id is not None:
  84. if rbd.use_margin or rbd.collision_shape == "MESH":
  85. col_shape['margin'] = rbd.collision_margin
  86. col_node['shape'] = "SubResource({})".format(shape_id)
  87. escn_file.add_node(col_node)
  88. return col_node
  89. def generate_convex_mesh_array(escn_file, export_settings, node):
  90. """Generates godots ConvexPolygonShape from an object"""
  91. mesh = node.data
  92. key = (mesh, "ConvexCollisionMesh")
  93. resource_id = escn_file.get_internal_resource(key)
  94. if resource_id is not None:
  95. return resource_id
  96. col_shape = InternalResource("ConvexPolygonShape", mesh.name)
  97. mesh = node.to_mesh(bpy.context.view_layer.depsgraph,
  98. apply_modifiers=True,
  99. calc_undeformed=True)
  100. # Triangulate
  101. triangulated_mesh = bmesh.new()
  102. triangulated_mesh.from_mesh(mesh)
  103. # For some reason, generateing the convex hull here causes Godot to crash
  104. # bmesh.ops.convex_hull(triangulated_mesh, input=triangulated_mesh.verts)
  105. bmesh.ops.triangulate(triangulated_mesh, faces=triangulated_mesh.faces)
  106. triangulated_mesh.to_mesh(mesh)
  107. triangulated_mesh.free()
  108. vert_array = list()
  109. for poly in mesh.polygons:
  110. for vert_id in poly.vertices:
  111. vert_array.append(list(mesh.vertices[vert_id].co))
  112. bpy.data.meshes.remove(mesh)
  113. col_shape['points'] = Array("PoolVector3Array(", values=vert_array)
  114. return col_shape, escn_file.add_internal_resource(col_shape, key)
  115. def generate_triangle_mesh_array(escn_file, export_settings, node):
  116. """Generates godots ConcavePolygonShape from an object"""
  117. mesh = node.data
  118. key = (mesh, "TriangleCollisionMesh")
  119. resource_id = escn_file.get_internal_resource(key)
  120. if resource_id is not None:
  121. return resource_id
  122. col_shape = InternalResource("ConcavePolygonShape", mesh.name)
  123. mesh = node.to_mesh(bpy.context.view_layer.depsgraph,
  124. apply_modifiers=True,
  125. calc_undeformed=True)
  126. # Triangulate
  127. triangulated_mesh = bmesh.new()
  128. triangulated_mesh.from_mesh(mesh)
  129. bmesh.ops.triangulate(triangulated_mesh, faces=triangulated_mesh.faces)
  130. triangulated_mesh.to_mesh(mesh)
  131. triangulated_mesh.free()
  132. vert_array = list()
  133. for poly in mesh.polygons:
  134. for vert_id in poly.vertices:
  135. vert_array.append(list(mesh.vertices[vert_id].co))
  136. bpy.data.meshes.remove(mesh)
  137. col_shape['data'] = Array("PoolVector3Array(", values=vert_array)
  138. return col_shape, escn_file.add_internal_resource(col_shape, key)
  139. def export_physics_controller(escn_file, export_settings, node,
  140. parent_gd_node):
  141. """Exports the physics body "type" as a separate node. In blender, the
  142. physics body type and the collision shape are one object, in godot they
  143. are two. This is the physics body type"""
  144. phys_name = node.name + 'Physics'
  145. rbd = node.rigid_body
  146. if rbd.type == "ACTIVE":
  147. if rbd.kinematic:
  148. phys_controller = 'KinematicBody'
  149. else:
  150. phys_controller = 'RigidBody'
  151. else:
  152. phys_controller = 'StaticBody'
  153. phys_obj = NodeTemplate(phys_name, phys_controller, parent_gd_node)
  154. # OPTIONS FOR ALL PHYSICS TYPES
  155. phys_obj['friction'] = rbd.friction
  156. phys_obj['bounce'] = rbd.restitution
  157. col_groups = 0
  158. for offset, flag in enumerate(rbd.collision_collections):
  159. col_groups += (1 if flag else 0) << offset
  160. phys_obj['transform'] = node.matrix_local
  161. phys_obj['collision_layer'] = col_groups
  162. phys_obj['collision_mask'] = col_groups
  163. if phys_controller == "RigidBody":
  164. phys_obj['can_sleep'] = rbd.use_deactivation
  165. phys_obj['linear_damp'] = rbd.linear_damping
  166. phys_obj['angular_damp'] = rbd.angular_damping
  167. phys_obj['sleeping'] = rbd.use_start_deactivated
  168. escn_file.add_node(phys_obj)
  169. return phys_obj
  170. def export_physics_properties(escn_file, export_settings, node,
  171. parent_gd_node):
  172. """Creates the necessary nodes for the physics"""
  173. parent_rbd = get_physics_root(node)
  174. if parent_rbd is None:
  175. parent_gd_node = export_physics_controller(
  176. escn_file, export_settings, node, parent_gd_node
  177. )
  178. # trace the path towards root, find the cloest physics node
  179. gd_node_ptr = parent_gd_node
  180. while gd_node_ptr.get_type() not in PHYSICS_TYPES:
  181. gd_node_ptr = gd_node_ptr.parent
  182. physics_gd_node = gd_node_ptr
  183. return export_collision_shape(
  184. escn_file, export_settings, node, physics_gd_node,
  185. parent_override=parent_rbd
  186. )