Skip to content

Commit

Permalink
KHR_collision_shapes has been refactored into KHR_implicit_shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
eoineoineoin committed Oct 14, 2024
1 parent 81df2e0 commit 7055335
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import bpy
from ...io.com.gltf2_io_collision_shapes import *
from ...io.com.gltf2_io_implicit_shapes import *
from ...io.com.gltf2_io_rigid_bodies import *
from io_scene_gltf2.io.com import gltf2_io
from mathutils import Matrix, Euler
Expand All @@ -9,6 +9,9 @@


class glTF2ExportUserExtension:
isExt: ImplicitShapesGlTFExtension
rbExt: RigidBodiesGlTFExtension

def __init__(self):
# We need to wait until we create the gltf2UserExtension to import the gltf2 modules
# Otherwise, it may fail because the gltf2 may not be loaded yet
Expand All @@ -19,7 +22,7 @@ def __init__(self):
self.ChildOfRootExtension = ChildOfRootExtension
self.properties = bpy.context.scene.khr_physics_exporter_props
self.rbExt = RigidBodiesGlTFExtension()
self.csExt = CollisionShapesGlTFExtension()
self.isExt = ImplicitShapesGlTFExtension()

# Supporting data allowing us to save joints correctly
self.blenderJointObjects = []
Expand All @@ -43,15 +46,15 @@ def gather_gltf_extensions_hook(self, gltf2_plan, export_settings):
gltf2_plan.extensions[rigidBody_Extension_Name] = physicsRootExtension

if (
not collisionGeom_Extension_Name in gltf2_plan.extensions
and self.csExt.should_export()
not implicitShapes_Extension_Name in gltf2_plan.extensions
and self.isExt.should_export()
):
cgRootExtension = self.Extension(
name=collisionGeom_Extension_Name,
extension=self.csExt.to_dict(),
isRootExtension = self.Extension(
name=implicitShapes_Extension_Name,
extension=self.isExt.to_dict(),
required=False,
)
gltf2_plan.extensions[collisionGeom_Extension_Name] = cgRootExtension
gltf2_plan.extensions[implicitShapes_Extension_Name] = isRootExtension

def gather_scene_hook(
self, gltf2_scene: gltf2_io.Scene, blender_scene, export_settings
Expand Down Expand Up @@ -317,16 +320,10 @@ def gather_node_hook2(self, gltf2_object, blender_object, export_settings):
extension_data.motion = motion

if blender_object.rigid_body:
shape_data = self._generateShapeData(
geom_data = self._generateGeometryData(
blender_object, gltf2_object, export_settings
)
if shape_data:
shape_obj = self.ChildOfRootExtension(
name=collisionGeom_Extension_Name,
path=["shapes"],
required=False,
extension=shape_data.to_dict(),
)
if geom_data:
filter_obj = self._generateFilterRootObject(blender_object)
extraProps = blender_object.khr_physics_extra_props

Expand All @@ -337,11 +334,11 @@ def gather_node_hook2(self, gltf2_object, blender_object, export_settings):

if extraProps.is_trigger:
extension_data.trigger = Trigger()
extension_data.trigger.shape = shape_obj
extension_data.trigger.geometry = geom_data
extension_data.trigger.collision_filter = filter_obj
else:
extension_data.collider = Collider()
extension_data.collider.shape = shape_obj
extension_data.collider.geometry = geom_data
extension_data.collider.collision_filter = filter_obj
extension_data.collider.physics_material = (
self._generateMaterialRootObject(blender_object)
Expand Down Expand Up @@ -584,24 +581,20 @@ def _generateFilterRootObject(self, node):
extension=collision_filter.to_dict(),
)

def _generateShapeData(self, node, glNode, export_settings):
def _generateGeometryData(self, node, glNode, export_settings) -> Optional[Geometry]:
if node.rigid_body == None or node.rigid_body.collision_shape == "COMPOUND":
return None
shape = Shape()
geom = Geometry()

if node.rigid_body.collision_shape == "CONVEX_HULL":
shape.type = "mesh"
shape.mesh = Mesh(glNode.mesh)
shape.extensions = {
rigidBody_Extension_Name: RigidBodiesShapeExtension(convexHull=True)
}
shape.mesh.skin = glNode.skin
return shape
elif node.rigid_body.collision_shape == "MESH":
shape.type = "mesh"
shape.mesh = Mesh(glNode.mesh)
shape.mesh.skin = glNode.skin
return shape
if node.rigid_body.collision_shape in ("CONVEX_HULL", "MESH"):
shape_node = self._constructNode("physicsMeshDataNode", Vector((0,0,0)), Quaternion((0,0,0,1)), export_settings)
shape_node.mesh = glNode.mesh
shape_node.skin = glNode.skin
geom.convex_hull = node.rigid_body.collision_shape == "CONVEX_HULL"
geom.node = shape_node
return geom

shape = Shape()
# If the shape is a geometric primitive, we may have to apply modifiers
# to see the final geometry. (glNode has already had modifiers applied)
with self._accessMeshData(node, export_settings) as meshData:
Expand Down Expand Up @@ -676,8 +669,8 @@ def _generateShapeData(self, node, glNode, export_settings):
)

node_ext = RigidBodiesNodeExtension()
shape_obj = self.ChildOfRootExtension(
name=collisionGeom_Extension_Name,
geom.shape = self.ChildOfRootExtension(
name=implicitShapes_Extension_Name,
path=["shapes"],
required=False,
extension=shape.to_dict(),
Expand All @@ -687,7 +680,7 @@ def _generateShapeData(self, node, glNode, export_settings):
node_ext.trigger.collision_filter = (
self._generateFilterRootObject(node)
)
node_ext.trigger.shape = shape_obj
node_ext.trigger.geometry = geom
else:
node_ext.collider = Collider()
node_ext.collider.physics_material = (
Expand All @@ -696,7 +689,7 @@ def _generateShapeData(self, node, glNode, export_settings):
node_ext.collider.collision_filter = (
self._generateFilterRootObject(node)
)
node_ext.collider.shape = shape_obj
node_ext.collider.geometry = geom

shape_alignment.extensions[rigidBody_Extension_Name] = (
self.Extension(
Expand All @@ -710,7 +703,13 @@ def _generateShapeData(self, node, glNode, export_settings):
# We've added the shape data to a child of glNode;
# return None so that the glNode doesn't get shape data,
return None
return shape
geom.shape = self.ChildOfRootExtension(
name=implicitShapes_Extension_Name,
path=["shapes"],
required=False,
extension=shape.to_dict(),
)
return geom

def _accessMeshData(self, node, export_settings):
"""RAII-style function to access mesh data with modifiers attached"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import bpy
from ...io.com.gltf2_io_collision_shapes import *
from ...io.com.gltf2_io_implicit_shapes import *
from ...io.com.gltf2_io_rigid_bodies import *
from typing import cast

Expand All @@ -11,8 +11,21 @@ def __init__(self, joint, connected_idx):
self.joint = joint
self.connected_idx = connected_idx

class ParentFixup:
"""Helper class to store information about mesh node parenting"""
def __init__(self, parent_node, child_idx, convex_hull):
self.parent_node = parent_node
self.child_idx = child_idx
self.convex_hull = convex_hull

class glTF2ImportUserExtension:
isExt: Optional[ImplicitShapesGlTFExtension] = None
rbExt: Optional[RigidBodiesGlTFExtension] = None
# Additional mapping to hook up joints
vnode_to_blender: dict = {}
joints_to_fixup: list[JointFixup] = []
parents_to_fixup: list[ParentFixup] = []

def __init__(self):
# We need to wait until we create the gltf2UserExtension to import the gltf2 modules
# Otherwise, it may fail because the gltf2 may not be loaded yet
Expand All @@ -24,16 +37,20 @@ def __init__(self):

self.properties = bpy.context.scene.khr_physics_exporter_props

# Additional mapping to hook up joints
self.vnode_to_blender = {}
self.joints_to_fixup = []

def gather_import_gltf_before_hook(self, gltf):
if not self.properties.enabled:
return

if not gltf.data.extensions:
return
csExt = gltf.data.extensions.get(collisionGeom_Extension_Name)
if csExt != None:
self.csExt = CollisionShapesGlTFExtension.from_dict(csExt)

self.vnode_to_blender = {}
self.joints_to_fixup = []
self.parents_to_fixup = []

isExt = gltf.data.extensions.get(implicitShapes_Extension_Name)
if isExt != None:
self.isExt = ImplicitShapesGlTFExtension.from_dict(isExt)
rbExt = gltf.data.extensions.get(rigidBody_Extension_Name)
if rbExt != None:
self.rbExt = RigidBodiesGlTFExtension.from_dict(rbExt)
Expand All @@ -55,6 +72,9 @@ def _find_parent_body(self, blender_node):
return None

def gather_import_scene_after_nodes_hook(self, gltf_scene, blender_scene, gltf):
if not self.properties.enabled:
return

for fixup in self.joints_to_fixup:
other_vnode = gltf.vnodes[fixup.connected_idx]
other = self.vnode_to_blender[other_vnode]
Expand All @@ -64,7 +84,21 @@ def gather_import_scene_after_nodes_hook(self, gltf_scene, blender_scene, gltf):
fixup.joint.rigid_body_constraint.object1 = body_a
fixup.joint.rigid_body_constraint.object2 = body_b

for fixup in self.parents_to_fixup:
other_vnode = gltf.vnodes[fixup.child_idx]
other = self.vnode_to_blender[other_vnode]
other.parent = fixup.parent_node
self._add_rigid_body(other)
if fixup.convex_hull:
other.rigid_body.collision_shape = "CONVEX_HULL"
else:
other.rigid_body.collision_shape = "MESH"
other.khr_physics_extra_props.non_renderable = True

def gather_import_node_after_hook(self, vnode, gltf_node, blender_object, gltf):
if not self.properties.enabled:
return

try:
self.gather_import_node_after_hook_2(vnode, gltf_node, blender_object, gltf)
except:
Expand All @@ -75,6 +109,9 @@ def gather_import_node_after_hook_2(self, vnode, gltf_node, blender_object, gltf
if not self.properties.enabled:
return

if self.rbExt == None:
return

self.vnode_to_blender[vnode] = blender_object

try:
Expand All @@ -90,23 +127,22 @@ def gather_import_node_after_hook_2(self, vnode, gltf_node, blender_object, gltf
or nodeExt.motion != None
):
if not blender_object.rigid_body:
# <todo.eoin This is the only way I've found to add a rigid body to a node
# There might be a cleaner way.
prev_active_objects = bpy.context.view_layer.objects.active
bpy.context.view_layer.objects.active = blender_object
bpy.ops.rigidbody.object_add()
bpy.context.view_layer.objects.active = prev_active_objects
self._add_rigid_body(blender_object)
blender_object.rigid_body.enabled = False # Static by default
blender_object.rigid_body.collision_shape = "COMPOUND"

colliderIdx = None
if nodeExt.trigger != None:
colliderIdx = nodeExt.trigger.shape
if nodeExt.collider != None:
colliderIdx = nodeExt.collider.shape
# This doesn't handle a node which has both a trigger and a collider
if nodeExt.trigger != None and nodeExt.trigger.geometry != None:
colliderIdx = nodeExt.trigger.geometry.shape
if nodeExt.collider != None and nodeExt.collider.geometry != None:
colliderIdx = nodeExt.collider.geometry.shape

if colliderIdx != None:
shape = self.csExt.shapes[cast(int, colliderIdx)]
if self.isExt == None:
# Shouldn't happen - referencing implicit shapes, but not in file
return
shape = self.isExt.shapes[cast(int, colliderIdx)]
if shape.sphere != None:
blender_object.rigid_body.collision_shape = "SPHERE"
if shape.box != None:
Expand Down Expand Up @@ -134,22 +170,20 @@ def gather_import_node_after_hook_2(self, vnode, gltf_node, blender_object, gltf
if shape.cylinder.radiusTop == 0:
blender_object.rigid_body.collision_shape = "CONE"

# <todo.eoin Figure out if we can hook in a different mesh/skin/weights
# other than the one associated with this node
if shape.mesh != None:
blender_object.rigid_body.collision_shape = "MESH"

if (
shape.extensions
and rigidBody_Extension_Name in shape.extensions
):
rbShapeExt = RigidBodiesShapeExtension.from_dict(
shape.extensions[rigidBody_Extension_Name]
)
if rbShapeExt.convexHull:
blender_object.rigid_body.collision_shape = "CONVEX_HULL"

# todo.eoin Collision systems
meshNodeIdx = None
convex_hull = False
if nodeExt.trigger != None and nodeExt.trigger.geometry != None:
meshNodeIdx = nodeExt.trigger.geometry.node
convex_hull = nodeExt.trigger.geometry.convex_hull
if nodeExt.collider != None and nodeExt.collider.geometry != None:
meshNodeIdx = nodeExt.collider.geometry.node
convex_hull = nodeExt.collider.geometry.convex_hull
if meshNodeIdx != None:
#todo: Handle the common case where the referenced node has the exact same mesh,
# transform, etc. - in this case, we can just use the existing node
self.parents_to_fixup.append(ParentFixup(blender_object, meshNodeIdx, convex_hull))

# todo.eoin Collision systems

if nodeExt.collider != None and nodeExt.collider.physics_material != None:
mat = self.rbExt.materials[cast(int, nodeExt.collider.physics_material)]
Expand Down Expand Up @@ -301,6 +335,14 @@ def gather_import_node_after_hook_2(self, vnode, gltf_node, blender_object, gltf
joint.spring_stiffness_ang_z = spring
joint.spring_damping_ang_x = damping

def _add_rigid_body(self, blender_object):
# <todo.eoin This is the only way I've found to add a rigid body to a node
# There might be a cleaner way.
prev_active_objects = bpy.context.view_layer.objects.active
bpy.context.view_layer.objects.active = blender_object
bpy.ops.rigidbody.object_add()
bpy.context.view_layer.objects.active = prev_active_objects

@staticmethod
def _populateDrive(
joint_extra,
Expand Down
Loading

0 comments on commit 7055335

Please sign in to comment.