{lyn2264} Adding Python API for Actor Group (#6064)
* {lyn2264} Adding Python API for Actor Group
* updating the IBoneData with behavior attributes
* adding actor_group.py for making actor group JSON data
* adding physics_data.py for making physics JSON data
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* fixing dictionary issues
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* updated comment style to numpydoc
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* updated to numpydoc style
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* example of actor group rules
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* Updated based on feedback
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:27f87510ae07771dbad3e430e31502b1d1d13dee868f143b7f1de2a7febc8eb9
|
||||
size 7046400
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "ScriptProcessorRule",
|
||||
"scriptFilename": "Assets/TestAnim/scene_export_actor.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import traceback, sys, uuid, os, json
|
||||
|
||||
#
|
||||
# Example for exporting ActorGroup scene rules
|
||||
#
|
||||
|
||||
def log_exception_traceback():
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
data = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
print(str(data))
|
||||
|
||||
def get_node_names(sceneGraph, nodeTypeName, testEndPoint = False, validList = None):
|
||||
import azlmbr.scene.graph
|
||||
import scene_api.scene_data
|
||||
|
||||
node = sceneGraph.get_root()
|
||||
nodeList = []
|
||||
children = []
|
||||
paths = []
|
||||
|
||||
while node.IsValid():
|
||||
# store children to process after siblings
|
||||
if sceneGraph.has_node_child(node):
|
||||
children.append(sceneGraph.get_node_child(node))
|
||||
|
||||
nodeName = scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node))
|
||||
paths.append(nodeName.get_path())
|
||||
|
||||
include = True
|
||||
|
||||
if (validList is not None):
|
||||
include = False # if a valid list filter provided, assume to not include node name
|
||||
name_parts = nodeName.get_path().split('.')
|
||||
for valid in validList:
|
||||
if (valid in name_parts[-1]):
|
||||
include = True
|
||||
break
|
||||
|
||||
# store any node that has provides specifc data content
|
||||
nodeContent = sceneGraph.get_node_content(node)
|
||||
if include and nodeContent.CastWithTypeName(nodeTypeName):
|
||||
if testEndPoint is not None:
|
||||
include = sceneGraph.is_node_end_point(node) is testEndPoint
|
||||
if include:
|
||||
if (len(nodeName.get_path())):
|
||||
nodeList.append(scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node)))
|
||||
|
||||
# advance to next node
|
||||
if sceneGraph.has_node_sibling(node):
|
||||
node = sceneGraph.get_node_sibling(node)
|
||||
elif children:
|
||||
node = children.pop()
|
||||
else:
|
||||
node = azlmbr.scene.graph.NodeIndex()
|
||||
|
||||
return nodeList, paths
|
||||
|
||||
def generate_mesh_group(scene, sceneManifest, meshDataList, paths):
|
||||
# Compute the name of the scene file
|
||||
clean_filename = scene.sourceFilename.replace('.', '_')
|
||||
mesh_group_name = os.path.basename(clean_filename)
|
||||
|
||||
# make the mesh group
|
||||
mesh_group = sceneManifest.add_mesh_group(mesh_group_name)
|
||||
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, clean_filename)) + '}'
|
||||
|
||||
# add all nodes to this mesh group
|
||||
for activeMeshIndex in range(len(meshDataList)):
|
||||
mesh_name = meshDataList[activeMeshIndex]
|
||||
mesh_path = mesh_name.get_path()
|
||||
sceneManifest.mesh_group_select_node(mesh_group, mesh_path)
|
||||
|
||||
def create_shape_configuration(nodeName):
|
||||
import scene_api.physics_data
|
||||
|
||||
if(nodeName in ['_foot_','_wrist_']):
|
||||
shapeConfiguration = scene_api.physics_data.BoxShapeConfiguration()
|
||||
shapeConfiguration.scale = [1.1, 1.1, 1.1]
|
||||
shapeConfiguration.dimensions = [2.1, 3.1, 4.1]
|
||||
return shapeConfiguration
|
||||
else:
|
||||
shapeConfiguration = scene_api.physics_data.CapsuleShapeConfiguration()
|
||||
shapeConfiguration.scale = [1.0, 1.0, 1.0]
|
||||
shapeConfiguration.height = 1.0
|
||||
shapeConfiguration.radius = 1.0
|
||||
return shapeConfiguration
|
||||
|
||||
def create_collider_configuration(nodeName):
|
||||
import scene_api.physics_data
|
||||
|
||||
colliderConfiguration = scene_api.physics_data.ColliderConfiguration()
|
||||
colliderConfiguration.Position = [0.1, 0.1, 0.2]
|
||||
colliderConfiguration.Rotation = [45.0, 35.0, 25.0]
|
||||
return colliderConfiguration
|
||||
|
||||
def generate_physics_nodes(actorPhysicsSetupRule, nodeNameList):
|
||||
import scene_api.physics_data
|
||||
|
||||
hitDetectionConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
simulatedObjectColliderConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
clothConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
ragdollConfig = scene_api.physics_data.RagdollConfiguration()
|
||||
|
||||
for nodeName in nodeNameList:
|
||||
shapeConfiguration = create_shape_configuration(nodeName)
|
||||
colliderConfiguration = create_collider_configuration(nodeName)
|
||||
hitDetectionConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
|
||||
simulatedObjectColliderConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
|
||||
clothConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
|
||||
#
|
||||
ragdollNode = scene_api.physics_data.RagdollNodeConfiguration()
|
||||
ragdollNode.JointConfig.Name = nodeName
|
||||
ragdollConfig.add_ragdoll_node_configuration(ragdollNode)
|
||||
ragdollConfig.colliders.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
|
||||
|
||||
actorPhysicsSetupRule.set_simulated_object_collider_config(simulatedObjectColliderConfig)
|
||||
actorPhysicsSetupRule.set_hit_detection_config(hitDetectionConfig)
|
||||
actorPhysicsSetupRule.set_cloth_config(clothConfig)
|
||||
actorPhysicsSetupRule.set_ragdoll_config(ragdollConfig)
|
||||
|
||||
def generate_actor_group(scene, sceneManifest, meshDataList, paths):
|
||||
import scene_api.scene_data
|
||||
import scene_api.physics_data
|
||||
import scene_api.actor_group
|
||||
|
||||
# fetch bone data
|
||||
validNames = ['_neck_','_pelvis_','_leg_','_knee_','_spine_','_arm_','_clavicle_','_head_','_elbow_','_wrist_']
|
||||
graph = scene_api.scene_data.SceneGraph(scene.graph)
|
||||
nodeList, allNodePaths = get_node_names(graph, 'BoneData', validList = validNames)
|
||||
|
||||
nodeNameList = []
|
||||
for activeMeshIndex, nodeName in enumerate(nodeList):
|
||||
nodeNameList.append(nodeName.get_name())
|
||||
|
||||
# add comment
|
||||
commentRule = scene_api.actor_group.CommentRule()
|
||||
commentRule.text = str(nodeNameList)
|
||||
|
||||
# ActorPhysicsSetupRule
|
||||
actorPhysicsSetupRule = scene_api.actor_group.ActorPhysicsSetupRule()
|
||||
generate_physics_nodes(actorPhysicsSetupRule, nodeNameList)
|
||||
|
||||
# add scale of the Actor rule
|
||||
actorScaleRule = scene_api.actor_group.ActorScaleRule()
|
||||
actorScaleRule.scaleFactor = 2.0
|
||||
|
||||
# add coordinate system rule
|
||||
coordinateSystemRule = scene_api.actor_group.CoordinateSystemRule()
|
||||
coordinateSystemRule.useAdvancedData = False
|
||||
|
||||
# add morph target rule
|
||||
morphTargetRule = scene_api.actor_group.MorphTargetRule()
|
||||
morphTargetRule.targets.select_targets([nodeNameList[0]], nodeNameList)
|
||||
|
||||
# add skeleton optimization rule
|
||||
skeletonOptimizationRule = scene_api.actor_group.SkeletonOptimizationRule()
|
||||
skeletonOptimizationRule.autoSkeletonLOD = True
|
||||
skeletonOptimizationRule.criticalBonesList.select_targets([nodeNameList[0:2]], nodeNameList)
|
||||
|
||||
# add LOD rule
|
||||
lodRule = scene_api.actor_group.LodRule()
|
||||
lodRule0 = lodRule.add_lod_level(0)
|
||||
lodRule0.select_targets([nodeNameList[1:4]], nodeNameList)
|
||||
|
||||
actorGroup = scene_api.actor_group.ActorGroup()
|
||||
actorGroup.name = os.path.basename(scene.sourceFilename)
|
||||
actorGroup.add_rule(actorScaleRule)
|
||||
actorGroup.add_rule(coordinateSystemRule)
|
||||
actorGroup.add_rule(skeletonOptimizationRule)
|
||||
actorGroup.add_rule(morphTargetRule)
|
||||
actorGroup.add_rule(lodRule)
|
||||
actorGroup.add_rule(actorPhysicsSetupRule)
|
||||
actorGroup.add_rule(commentRule)
|
||||
sceneManifest.manifest['values'].append(actorGroup.to_dict())
|
||||
|
||||
def update_manifest(scene):
|
||||
import json, uuid, os
|
||||
import azlmbr.scene.graph
|
||||
import scene_api.scene_data
|
||||
|
||||
graph = scene_api.scene_data.SceneGraph(scene.graph)
|
||||
mesh_name_list, all_node_paths = get_node_names(graph, 'MeshData')
|
||||
scene_manifest = scene_api.scene_data.SceneManifest()
|
||||
generate_actor_group(scene, scene_manifest, mesh_name_list, all_node_paths)
|
||||
generate_mesh_group(scene, scene_manifest, mesh_name_list, all_node_paths)
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
return scene_manifest.export()
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
log_exception_traceback()
|
||||
except:
|
||||
log_exception_traceback()
|
||||
|
||||
global sceneJobHandler
|
||||
sceneJobHandler.disconnect()
|
||||
sceneJobHandler = None
|
||||
|
||||
# try to create SceneAPI handler for processing
|
||||
try:
|
||||
import azlmbr.scene
|
||||
|
||||
sceneJobHandler = azlmbr.scene.ScriptBuildingNotificationBusHandler()
|
||||
sceneJobHandler.connect()
|
||||
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
|
||||
except:
|
||||
sceneJobHandler = None
|
||||
@@ -46,6 +46,9 @@ namespace AZ
|
||||
BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context);
|
||||
if (behaviorContext)
|
||||
{
|
||||
behaviorContext->Class<SceneAPI::DataTypes::IBoneData>()
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "scene");
|
||||
behaviorContext->Class<AZ::SceneData::GraphData::BoneData>()
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import os, sys
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
import physics_data
|
||||
|
||||
class ActorGroup():
|
||||
"""
|
||||
Configure actor data exporting.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name:
|
||||
Name for the group. This name will also be used as the name for the generated file.
|
||||
|
||||
selectedRootBone:
|
||||
The root bone of the animation that will be exported.
|
||||
|
||||
rules: `list` of actor rules (derived from BaseRule)
|
||||
modifiers to fine-tune the export process.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
|
||||
add_rule(rule)
|
||||
Adds a rule into the internal rules container
|
||||
Returns True if the rule was added to the internal rules container
|
||||
|
||||
create_rule(rule)
|
||||
Helper method to add and return the rule
|
||||
|
||||
remove_rule(type)
|
||||
Removes the rule from the internal rules container
|
||||
|
||||
to_dict()
|
||||
Converts the contents to as a Python dictionary
|
||||
|
||||
to_json()
|
||||
Converts the contents to a JSON string
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.typename = 'ActorGroup'
|
||||
self.name = ''
|
||||
self.selectedRootBone = ''
|
||||
self.id = uuid.uuid4()
|
||||
self.rules = set()
|
||||
|
||||
def add_rule(self, rule) -> bool:
|
||||
if (rule not in self.rules):
|
||||
self.rules.add(rule)
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_rule(self, rule) -> any:
|
||||
if (self.add_rule(rule)):
|
||||
return rule
|
||||
return None
|
||||
|
||||
def remove_rule(self, type) -> None:
|
||||
self.rules.discard(rule)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
out = {}
|
||||
out['$type'] = self.typename
|
||||
out['name'] = self.name
|
||||
out['selectedRootBone'] = self.selectedRootBone
|
||||
out['id'] = f"{{{str(self.id)}}}"
|
||||
# convert the rules
|
||||
ruleList = []
|
||||
for rule in self.rules:
|
||||
jsonStr = json.dumps(rule, cls=RuleEncoder)
|
||||
jsonDict = json.loads(jsonStr)
|
||||
ruleList.append(jsonDict)
|
||||
out['rules'] = ruleList
|
||||
return out
|
||||
|
||||
def to_json(self) -> any:
|
||||
jsonDOM = self.to_dict()
|
||||
return json.dumps(jsonDOM, cls=RuleEncoder, indent=1)
|
||||
|
||||
class RuleEncoder(json.JSONEncoder):
|
||||
"""
|
||||
A helper class to encode the Python classes with to a Python dictionary
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
encode(obj)
|
||||
Converts contents to a Python dictionary for the JSONEncoder
|
||||
|
||||
"""
|
||||
def encode(self, obj):
|
||||
chunk = obj
|
||||
if isinstance(obj, BaseRule):
|
||||
chunk = obj.to_dict()
|
||||
elif isinstance(obj, dict):
|
||||
chunk = obj
|
||||
elif hasattr(obj, 'to_dict'):
|
||||
chunk = obj.to_dict()
|
||||
else:
|
||||
chunk = obj.__dict__
|
||||
|
||||
return super().encode(chunk)
|
||||
|
||||
class BaseRule():
|
||||
"""
|
||||
Base class of the actor rules to help encode the type name of abstract rules
|
||||
|
||||
Parameters
|
||||
----------
|
||||
typename : str
|
||||
A typename the $type will be in the JSON chunk object
|
||||
|
||||
Attributes
|
||||
----------
|
||||
typename: str
|
||||
The type name of the abstract classes to be serialized
|
||||
|
||||
id: UUID
|
||||
a unique ID for the rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
Adds the '$type' member
|
||||
Adds a random 'id' member
|
||||
"""
|
||||
def __init__(self, typename):
|
||||
self.typename = typename
|
||||
self.id = uuid.uuid4()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id.__eq__(other.id)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__eq__(other) is False
|
||||
|
||||
def __hash__(self):
|
||||
return self.id.__hash__()
|
||||
|
||||
def to_dict(self):
|
||||
data = self.__dict__
|
||||
data['id'] = f"{{{str(self.id)}}}"
|
||||
# rename 'typename' to '$type'
|
||||
data['$type'] = self.typename
|
||||
data.pop('typename')
|
||||
return data
|
||||
|
||||
class SceneNodeSelectionList(BaseRule):
|
||||
"""
|
||||
Contains a list of node names to include (selectedNodes) and to exclude (unselectedNodes)
|
||||
|
||||
Attributes
|
||||
----------
|
||||
selectedNodes: `list` of str
|
||||
The node names to include for this group rule
|
||||
|
||||
unselectedNodes: `list` of str
|
||||
The node names to exclude for this group rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
convert_selection(self, container, key):
|
||||
this adds its contents to an existing dictionary container at a key position
|
||||
|
||||
select_targets(self, selectedList, allNodesList:list)
|
||||
helper function to include a small list of node names from list of all the node names
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('SceneNodeSelectionList')
|
||||
self.selectedNodes = []
|
||||
self.unselectedNodes = []
|
||||
|
||||
def convert_selection(self, container, key):
|
||||
container[key] = self.to_dict()
|
||||
|
||||
def select_targets(self, selectedList, allNodesList:list):
|
||||
self.selectedNodes = selectedList
|
||||
self.unselectedNodes = allNodesList.copy()
|
||||
for node in selectedList:
|
||||
if node in self.unselectedNodes:
|
||||
self.unselectedNodes.remove(node)
|
||||
|
||||
class LodNodeSelectionList(SceneNodeSelectionList):
|
||||
"""
|
||||
Level of Detail node selection list
|
||||
|
||||
The selected nodes should be joints with BoneData
|
||||
derived from SceneNodeSelectionList
|
||||
see also LodRule
|
||||
|
||||
Attributes
|
||||
----------
|
||||
lodLevel: int
|
||||
the level of detail to target where 0 is nearest level of detail
|
||||
up to 5 being the farthest level of detail range for 6 levels maximum
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.typename = 'LodNodeSelectionList'
|
||||
self.lodLevel = 0
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['lodLevel'] = self.lodLevel
|
||||
return data
|
||||
|
||||
class PhysicsAnimationConfiguration():
|
||||
"""
|
||||
Configuration for animated physics structures which are more detailed than the character controller.
|
||||
For example, ragdoll or hit detection configurations.
|
||||
See also 'class Physics::AnimationConfiguration'
|
||||
|
||||
Attributes
|
||||
----------
|
||||
hitDetectionConfig: CharacterColliderConfiguration
|
||||
for hit detection
|
||||
|
||||
ragdollConfig: RagdollConfiguration
|
||||
to set up physics properties
|
||||
|
||||
clothConfig: CharacterColliderConfiguration
|
||||
for cloth physics
|
||||
|
||||
simulatedObjectColliderConfig: CharacterColliderConfiguration
|
||||
for simulation physics
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.hitDetectionConfig = physics_data.CharacterColliderConfiguration()
|
||||
self.ragdollConfig = physics_data.RagdollConfiguration()
|
||||
self.clothConfig = physics_data.CharacterColliderConfiguration()
|
||||
self.simulatedObjectColliderConfig = physics_data.CharacterColliderConfiguration()
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
data["hitDetectionConfig"] = self.hitDetectionConfig.to_dict()
|
||||
data["ragdollConfig"] = self.ragdollConfig.to_dict()
|
||||
data["clothConfig"] = self.clothConfig.to_dict()
|
||||
data["simulatedObjectColliderConfig"] = self.simulatedObjectColliderConfig.to_dict()
|
||||
return data
|
||||
|
||||
class EMotionFXPhysicsSetup():
|
||||
"""
|
||||
Physics setup properties
|
||||
See also 'class EMotionFX::PhysicsSetup'
|
||||
|
||||
Attributes
|
||||
----------
|
||||
config: PhysicsAnimationConfiguration
|
||||
Configuration to setup physics properties
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.typename = 'PhysicsSetup'
|
||||
self.config = PhysicsAnimationConfiguration()
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"config" : self.config.to_dict()
|
||||
}
|
||||
|
||||
class ActorPhysicsSetupRule(BaseRule):
|
||||
"""
|
||||
Physics setup properties
|
||||
|
||||
Attributes
|
||||
----------
|
||||
data: EMotionFXPhysicsSetup
|
||||
Data to setup physics properties
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
set_hit_detection_config(self, hitDetectionConfig)
|
||||
Simple helper function to assign the hit detection configuration
|
||||
|
||||
set_ragdoll_config(self, ragdollConfig)
|
||||
Simple helper function to assign the ragdoll configuration
|
||||
|
||||
set_cloth_config(self, clothConfig)
|
||||
Simple helper function to assign the cloth configuration
|
||||
|
||||
set_simulated_object_collider_config(self, simulatedObjectColliderConfig)
|
||||
Simple helper function to assign the assign simulated object collider configuration
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('ActorPhysicsSetupRule')
|
||||
self.data = EMotionFXPhysicsSetup()
|
||||
|
||||
def set_hit_detection_config(self, hitDetectionConfig) -> None:
|
||||
self.data.config.hitDetectionConfig = hitDetectionConfig
|
||||
|
||||
def set_ragdoll_config(self, ragdollConfig) -> None:
|
||||
self.data.config.ragdollConfig = ragdollConfig
|
||||
|
||||
def set_cloth_config(self, clothConfig) -> None:
|
||||
self.data.config.clothConfig = clothConfig
|
||||
|
||||
def set_simulated_object_collider_config(self, simulatedObjectColliderConfig) -> None:
|
||||
self.data.config.simulatedObjectColliderConfig = simulatedObjectColliderConfig
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data["data"] = self.data.to_dict()
|
||||
return data
|
||||
|
||||
class ActorScaleRule(BaseRule):
|
||||
"""
|
||||
Scale the actor
|
||||
|
||||
Attributes
|
||||
----------
|
||||
scaleFactor: float
|
||||
Set the multiplier to scale geometry.
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('ActorScaleRule')
|
||||
self.scaleFactor = 1.0
|
||||
|
||||
class CoordinateSystemRule(BaseRule):
|
||||
"""
|
||||
Modify the target coordinate system, applying a transformation to all data (transforms and vertex data if it exists).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
targetCoordinateSystem: int
|
||||
Change the direction the actor/motion will face by applying a post transformation to the data.
|
||||
|
||||
useAdvancedData: bool
|
||||
If True, use advanced settings
|
||||
|
||||
originNodeName: str
|
||||
Select a Node from the scene as the origin for this export.
|
||||
|
||||
rotation: [float, float, float, float]
|
||||
Sets the orientation offset of the processed mesh in degrees. Rotates (yaw, pitch, roll) the group after translation.
|
||||
|
||||
translation: [float, float, float]
|
||||
Moves the group along the given vector3.
|
||||
|
||||
scale: float
|
||||
Sets the scale offset of the processed mesh.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CoordinateSystemRule')
|
||||
self.targetCoordinateSystem = 0
|
||||
self.useAdvancedData = False
|
||||
self.originNodeName = ''
|
||||
self.rotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.translation = [0.0, 0.0, 0.0]
|
||||
self.scale = 1.0
|
||||
|
||||
class SkeletonOptimizationRule(BaseRule):
|
||||
"""
|
||||
Advanced skeleton optimization rule.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
autoSkeletonLOD: bool
|
||||
Client side skeleton LOD based on skinning information and critical bones list.
|
||||
|
||||
serverSkeletonOptimization: bool
|
||||
Server side skeleton optimization based on hit detections and critical bones list.
|
||||
|
||||
criticalBonesList: `list` of SceneNodeSelectionList
|
||||
Bones in this list will not be optimized out.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('SkeletonOptimizationRule')
|
||||
self.autoSkeletonLOD = True
|
||||
self.serverSkeletonOptimization = False
|
||||
self.criticalBonesList = SceneNodeSelectionList()
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
self.criticalBonesList.convert_selection(data, 'criticalBonesList')
|
||||
return data
|
||||
|
||||
class LodRule(BaseRule):
|
||||
"""
|
||||
Set up the level of detail for the meshes in this group.
|
||||
|
||||
The engine supports 6 total lods.
|
||||
1 for the base model then 5 more lods.
|
||||
The rule only captures lods past level 0 so this is set to 5.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nodeSelectionList: `list` of LodNodeSelectionList
|
||||
Select the meshes to assign to each level of detail.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
add_lod_level(lodLevel, selectedNodes=None, unselectedNodes=None)
|
||||
A helper function to add selected nodes (list of node names) and unselected nodes (list of node names)
|
||||
This creates a LodNodeSelectionList and adds it to the node selection list
|
||||
returns the LodNodeSelectionList that was created
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('{3CB103B3-CEAF-49D7-A9DC-5A31E2DF15E4} LodRule')
|
||||
self.nodeSelectionList = [] # list of LodNodeSelectionList
|
||||
|
||||
def add_lod_level(self, lodLevel, selectedNodes=None, unselectedNodes=None) -> LodNodeSelectionList:
|
||||
lodNodeSelection = LodNodeSelectionList()
|
||||
lodNodeSelection.selectedNodes = selectedNodes
|
||||
lodNodeSelection.unselectedNodes = unselectedNodes
|
||||
lodNodeSelection.lodLevel = lodLevel
|
||||
self.nodeSelectionList.append(lodNodeSelection)
|
||||
return lodNodeSelection
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
selectionListList = data.pop('nodeSelectionList')
|
||||
data['nodeSelectionList'] = []
|
||||
for nodeList in selectionListList:
|
||||
data['nodeSelectionList'].append(nodeList.to_dict())
|
||||
return data
|
||||
|
||||
class MorphTargetRule(BaseRule):
|
||||
"""
|
||||
Select morph targets for actor.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
targets: `list` of SceneNodeSelectionList
|
||||
Select 1 or more meshes to include in the actor as morph targets.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MorphTargetRule')
|
||||
self.targets = SceneNodeSelectionList()
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
self.targets.convert_selection(data, 'targets')
|
||||
return data
|
||||
|
||||
class CommentRule(BaseRule):
|
||||
"""
|
||||
Add an optional comment to the asset's properties.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
text: str
|
||||
Text for the comment.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CommentRule')
|
||||
self.text = ''
|
||||
@@ -0,0 +1,580 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Shape.h
|
||||
|
||||
class ColliderConfiguration():
|
||||
"""
|
||||
Configuration for a collider
|
||||
|
||||
Attributes
|
||||
----------
|
||||
Trigger: bool
|
||||
Should this shape act as a trigger shape.
|
||||
|
||||
Simulated: bool
|
||||
Should this shape partake in collision in the physical simulation.
|
||||
|
||||
InSceneQueries: bool
|
||||
Should this shape partake in scene queries (ray casts, overlap tests, sweeps).
|
||||
|
||||
Exclusive: bool
|
||||
Can this collider be shared between multiple bodies?
|
||||
|
||||
Position: [float, float, float] Vector3
|
||||
Shape offset relative to the connected rigid body.
|
||||
|
||||
Rotation: [float, float, float, float] Quaternion
|
||||
Shape rotation relative to the connected rigid body.
|
||||
|
||||
ColliderTag: str
|
||||
Identification tag for the collider.
|
||||
|
||||
RestOffset: float
|
||||
Bodies will come to rest separated by the sum of their rest offsets.
|
||||
|
||||
ContactOffset: float
|
||||
Bodies will start to generate contacts when closer than the sum of their contact offsets.
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.Trigger = True
|
||||
self.Simulated = True
|
||||
self.InSceneQueries = True
|
||||
self.Exclusive = True
|
||||
self.Position = [0.0, 0.0, 0.0]
|
||||
self.Rotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.ColliderTag = ''
|
||||
self.RestOffset = 0.0
|
||||
self.ContactOffset = 0.02
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Character.h
|
||||
|
||||
class CharacterColliderNodeConfiguration():
|
||||
"""
|
||||
Shapes to define the animation's to model of physics
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name : str
|
||||
debug name of the node
|
||||
|
||||
shapes : `list` of `tuple` of (ColliderConfiguration, ShapeConfiguration)
|
||||
a list of pairs of collider and shape configuration
|
||||
|
||||
Methods
|
||||
-------
|
||||
add_collider_shape_pair(colliderConfiguration, shapeConfiguration)
|
||||
Helper function to add a collider and shape configuration at the same time
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.shapes = [] # List of Tuple of (ColliderConfiguration, ShapeConfiguration)
|
||||
|
||||
def add_collider_shape_pair(self, colliderConfiguration, shapeConfiguration) -> None:
|
||||
pair = (colliderConfiguration, shapeConfiguration)
|
||||
self.shapes.append(pair)
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
shapeList = []
|
||||
for index, shape in enumerate(self.shapes):
|
||||
tupleValue = (shape[0].to_dict(), # ColliderConfiguration
|
||||
shape[1].to_dict()) # ShapeConfiguration
|
||||
shapeList.append(tupleValue)
|
||||
data['name'] = self.name
|
||||
data['shapes'] = shapeList
|
||||
return data
|
||||
|
||||
class CharacterColliderConfiguration():
|
||||
"""
|
||||
Information required to create the basic physics representation of a character.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nodes : `list` of CharacterColliderNodeConfiguration
|
||||
a list of CharacterColliderNodeConfiguration nodes
|
||||
|
||||
Methods
|
||||
-------
|
||||
add_character_collider_node_configuration(colliderConfiguration, shapeConfiguration)
|
||||
Helper function to add a character collider node configuration into the nodes
|
||||
|
||||
add_character_collider_node_configuration_node(name, colliderConfiguration, shapeConfiguration)
|
||||
Helper function to add a character collider node configuration into the nodes
|
||||
|
||||
**Returns**: CharacterColliderNodeConfiguration
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.nodes = [] # list of CharacterColliderNodeConfiguration
|
||||
|
||||
def add_character_collider_node_configuration(self, characterColliderNodeConfiguration) -> None:
|
||||
self.nodes.append(characterColliderNodeConfiguration)
|
||||
|
||||
def add_character_collider_node_configuration_node(self, name, colliderConfiguration, shapeConfiguration) -> CharacterColliderNodeConfiguration:
|
||||
characterColliderNodeConfiguration = CharacterColliderNodeConfiguration()
|
||||
self.add_character_collider_node_configuration(characterColliderNodeConfiguration)
|
||||
characterColliderNodeConfiguration.name = name
|
||||
characterColliderNodeConfiguration.add_collider_shape_pair(colliderConfiguration, shapeConfiguration)
|
||||
return characterColliderNodeConfiguration
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
nodeList = []
|
||||
for node in self.nodes:
|
||||
nodeList.append(node.to_dict())
|
||||
data['nodes'] = nodeList
|
||||
return data
|
||||
|
||||
# see Code\Framework\AzFramework\AzFramework\Physics\ShapeConfiguration.h for underlying data structures
|
||||
|
||||
class ShapeConfiguration():
|
||||
"""
|
||||
Base class for all the shape collider configurations
|
||||
|
||||
Attributes
|
||||
----------
|
||||
scale : [float, float, float]
|
||||
a 3-element list to describe the scale along the X, Y, and Z axises such as [1.0, 1.0, 1.0]
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self, shapeType):
|
||||
self._shapeType = shapeType
|
||||
self.scale = [1.0, 1.0, 1.0]
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"$type": self._shapeType,
|
||||
"Scale": self.scale
|
||||
}
|
||||
|
||||
class SphereShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Sphere collider
|
||||
|
||||
Attributes
|
||||
----------
|
||||
radius: float
|
||||
a scalar value to define the radius of the sphere
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('SphereShapeConfiguration')
|
||||
self.radius = 0.5
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['Radius'] = self.radius
|
||||
return data
|
||||
|
||||
class BoxShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Box collider
|
||||
|
||||
Attributes
|
||||
----------
|
||||
dimensions: [float, float, float]
|
||||
The width, height, and depth dimensions of the Box collider
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('BoxShapeConfiguration')
|
||||
self.dimensions = [1.0, 1.0, 1.0]
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['Configuration'] = self.dimensions
|
||||
return data
|
||||
|
||||
class CapsuleShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Capsule collider
|
||||
|
||||
Attributes
|
||||
----------
|
||||
height: float
|
||||
The height of the Capsule
|
||||
|
||||
radius: float
|
||||
The radius of the Capsule
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CapsuleShapeConfiguration')
|
||||
self.height = 1.00
|
||||
self.radius = 0.25
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['Height'] = self.height
|
||||
data['Radius'] = self.radius
|
||||
return data
|
||||
|
||||
class PhysicsAssetShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Asset collider using a mesh asset for collision
|
||||
|
||||
Attributes
|
||||
----------
|
||||
asset: { "assetHint": assetReference }
|
||||
the name of the asset to load for collision information
|
||||
|
||||
assetScale: [float, float, float]
|
||||
The scale of the asset shape such as [1.0, 1.0, 1.0]
|
||||
|
||||
useMaterialsFromAsset: bool
|
||||
Auto-set physics materials using asset's physics material names
|
||||
|
||||
subdivisionLevel: int
|
||||
The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling.
|
||||
|
||||
Methods
|
||||
-------
|
||||
set_asset_reference(self, assetReference: str)
|
||||
Helper function to set the asset reference to the collision mesh such as 'my/folder/my_mesh.azmodel'
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('PhysicsAssetShapeConfiguration')
|
||||
self.asset = {}
|
||||
self.assetScale = [1.0, 1.0, 1.0]
|
||||
self.useMaterialsFromAsset = True
|
||||
self.subdivisionLevel = 4
|
||||
|
||||
def set_asset_reference(self, assetReference: str) -> None:
|
||||
self.asset = { "assetHint": assetReference }
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['PhysicsAsset'] = self.asset
|
||||
data['AssetScale'] = self.assetScale
|
||||
data['UseMaterialsFromAsset'] = self.useMaterialsFromAsset
|
||||
data['SubdivisionLevel'] = self.subdivisionLevel
|
||||
return data
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Configuration\JointConfiguration.h
|
||||
|
||||
class JointConfiguration():
|
||||
"""
|
||||
The joint configuration
|
||||
|
||||
see also: class AzPhysics::JointConfiguration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
Name: str
|
||||
For debugging/tracking purposes only.
|
||||
|
||||
ParentLocalRotation: [float, float, float, float]
|
||||
Parent joint frame relative to parent body.
|
||||
|
||||
ParentLocalPosition: [float, float, float]
|
||||
Joint position relative to parent body.
|
||||
|
||||
ChildLocalRotation: [float, float, float, float]
|
||||
Child joint frame relative to child body.
|
||||
|
||||
ChildLocalPosition: [float, float, float]
|
||||
Joint position relative to child body.
|
||||
|
||||
StartSimulationEnabled: bool
|
||||
When active, the joint will be enabled when the simulation begins.
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.Name = ''
|
||||
self.ParentLocalRotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.ParentLocalPosition = [0.0, 0.0, 0.0]
|
||||
self.ChildLocalRotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.ChildLocalPosition = [0.0, 0.0, 0.0]
|
||||
self.StartSimulationEnabled = True
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Configuration\SimulatedBodyConfiguration.h
|
||||
|
||||
class SimulatedBodyConfiguration():
|
||||
"""
|
||||
Base Class of all Physics Bodies that will be simulated.
|
||||
|
||||
see also: class AzPhysics::SimulatedBodyConfiguration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: str
|
||||
For debugging/tracking purposes only.
|
||||
|
||||
position: [float, float, float]
|
||||
starting position offset
|
||||
|
||||
orientation: [float, float, float, float]
|
||||
starting rotation (Quaternion)
|
||||
|
||||
startSimulationEnabled: bool
|
||||
to start when simulation engine starts
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.position = [0.0, 0.0, 0.0]
|
||||
self.orientation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.startSimulationEnabled = True
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"name" : self.name,
|
||||
"position" : self.position,
|
||||
"orientation" : self.orientation,
|
||||
"startSimulationEnabled" : self.startSimulationEnabled
|
||||
}
|
||||
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Configuration\RigidBodyConfiguration.h
|
||||
|
||||
class RigidBodyConfiguration(SimulatedBodyConfiguration):
|
||||
"""
|
||||
PhysX Rigid Body Configuration
|
||||
|
||||
see also: class AzPhysics::RigidBodyConfiguration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
initialLinearVelocity: [float, float, float]
|
||||
Linear velocity applied when the rigid body is activated.
|
||||
|
||||
initialAngularVelocity: [float, float, float]
|
||||
Angular velocity applied when the rigid body is activated (limited by maximum angular velocity)
|
||||
|
||||
centerOfMassOffset: [float, float, float]
|
||||
Local space offset for the center of mass (COM).
|
||||
|
||||
mass: float
|
||||
The mass of the rigid body in kilograms.
|
||||
A value of 0 is treated as infinite.
|
||||
The trajectory of infinite mass bodies cannot be affected by any collisions or forces other than gravity.
|
||||
|
||||
linearDamping: float
|
||||
The rate of decay over time for linear velocity even if no forces are acting on the rigid body.
|
||||
|
||||
angularDamping: float
|
||||
The rate of decay over time for angular velocity even if no forces are acting on the rigid body.
|
||||
|
||||
sleepMinEnergy: float
|
||||
The rigid body can go to sleep (settle) when kinetic energy per unit mass is persistently below this value.
|
||||
|
||||
maxAngularVelocity: float
|
||||
Clamp angular velocities to this maximum value.
|
||||
|
||||
startAsleep: bool
|
||||
When active, the rigid body will be asleep when spawned, and wake when the body is disturbed.
|
||||
|
||||
interpolateMotion: bool
|
||||
When active, simulation results are interpolated resulting in smoother motion.
|
||||
|
||||
gravityEnabled: bool
|
||||
When active, global gravity affects this rigid body.
|
||||
|
||||
kinematic: bool
|
||||
When active, the rigid body is not affected by gravity or other forces and is moved by script.
|
||||
|
||||
ccdEnabled: bool
|
||||
When active, the rigid body has continuous collision detection (CCD).
|
||||
Use this to ensure accurate collision detection, particularly for fast moving rigid bodies.
|
||||
CCD must be activated in the global PhysX preferences.
|
||||
|
||||
ccdMinAdvanceCoefficient: float
|
||||
Coefficient affecting how granularly time is subdivided in CCD.
|
||||
|
||||
ccdFrictionEnabled: bool
|
||||
Whether friction is applied when resolving CCD collisions.
|
||||
|
||||
computeCenterOfMass: bool
|
||||
Compute the center of mass (COM) for this rigid body.
|
||||
|
||||
computeInertiaTensor: bool
|
||||
When active, inertia is computed based on the mass and shape of the rigid body.
|
||||
|
||||
computeMass: bool
|
||||
When active, the mass of the rigid body is computed based on the volume and density values of its colliders.
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Basic initial settings.
|
||||
self.initialLinearVelocity = [0.0, 0.0, 0.0]
|
||||
self.initialAngularVelocity = [0.0, 0.0, 0.0]
|
||||
self.centerOfMassOffset = [0.0, 0.0, 0.0]
|
||||
|
||||
# Simulation parameters.
|
||||
self.mass = 1.0
|
||||
self.linearDamping = 0.05
|
||||
self.angularDamping = 0.15
|
||||
self.sleepMinEnergy = 0.005
|
||||
self.maxAngularVelocity = 100.0
|
||||
self.startAsleep = False
|
||||
self.interpolateMotion = False
|
||||
self.gravityEnabled = True
|
||||
self.kinematic = False
|
||||
self.ccdEnabled = False
|
||||
self.ccdMinAdvanceCoefficient = 0.15
|
||||
self.ccdFrictionEnabled = False
|
||||
self.computeCenterOfMass = True
|
||||
self.computeInertiaTensor = True
|
||||
self.computeMass = True
|
||||
|
||||
# Flags to restrict motion along specific world-space axes.
|
||||
self.lockLinearX = False
|
||||
self.lockLinearY = False
|
||||
self.lockLinearZ = False
|
||||
|
||||
# Flags to restrict rotation around specific world-space axes.
|
||||
self.lockAngularX = False
|
||||
self.lockAngularY = False
|
||||
self.lockAngularZ = False
|
||||
|
||||
# If set, non-simulated shapes will also be included in the mass properties calculation.
|
||||
self.includeAllShapesInMassCalculation = False
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data["Initial linear velocity"] = self.initialLinearVelocity
|
||||
data["Initial angular velocity"] = self.initialAngularVelocity
|
||||
data["Linear damping"] = self.linearDamping
|
||||
data["Angular damping"] = self.angularDamping
|
||||
data["Sleep threshold"] = self.sleepMinEnergy
|
||||
data["Start Asleep"] = self.startAsleep
|
||||
data["Interpolate Motion"] = self.interpolateMotion
|
||||
data["Gravity Enabled"] = self.gravityEnabled
|
||||
data["Kinematic"] = self.kinematic
|
||||
data["CCD Enabled"] = self.ccdEnabled
|
||||
data["Compute Mass"] = self.computeMass
|
||||
data["Lock Linear X"] = self.lockLinearX
|
||||
data["Lock Linear Y"] = self.lockLinearY
|
||||
data["Lock Linear Z"] = self.lockLinearZ
|
||||
data["Lock Angular X"] = self.lockAngularX
|
||||
data["Lock Angular Y"] = self.lockAngularY
|
||||
data["Lock Angular Z"] = self.lockAngularZ
|
||||
data["Mass"] = self.mass
|
||||
data["Compute COM"] = self.computeCenterOfMass
|
||||
data["Centre of mass offset"] = self.centerOfMassOffset
|
||||
data["Compute inertia"] = self.computeInertiaTensor
|
||||
data["Maximum Angular Velocity"] = self.maxAngularVelocity
|
||||
data["Include All Shapes In Mass"] = self.includeAllShapesInMassCalculation
|
||||
data["CCD Min Advance"] = self.ccdMinAdvanceCoefficient
|
||||
data["CCD Friction"] = self.ccdFrictionEnabled
|
||||
return data
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Ragdoll.h
|
||||
|
||||
class RagdollNodeConfiguration(RigidBodyConfiguration):
|
||||
"""
|
||||
Ragdoll node Configuration
|
||||
|
||||
see also: class Physics::RagdollConfiguration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
JointConfig: JointConfiguration
|
||||
Ragdoll joint node configuration
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.JointConfig = JointConfiguration()
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['JointConfig'] = self.JointConfig.to_dict()
|
||||
return data
|
||||
|
||||
class RagdollConfiguration():
|
||||
"""
|
||||
A configuration of join nodes and a character collider configuration for a ragdoll
|
||||
|
||||
see also: class Physics::RagdollConfiguration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nodes: `list` of RagdollNodeConfiguration
|
||||
A list of RagdollNodeConfiguration entries
|
||||
|
||||
colliders: CharacterColliderConfiguration
|
||||
A CharacterColliderConfiguration
|
||||
|
||||
Methods
|
||||
-------
|
||||
add_ragdoll_node_configuration(ragdollNodeConfiguration)
|
||||
Helper function to add a single ragdoll node configuration (normally for each joint/bone node)
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.nodes = [] # list of RagdollNodeConfiguration
|
||||
self.colliders = CharacterColliderConfiguration()
|
||||
|
||||
def add_ragdoll_node_configuration(self, ragdollNodeConfiguration) -> None:
|
||||
self.nodes.append(ragdollNodeConfiguration)
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
nodeList = []
|
||||
for index, node in enumerate(self.nodes):
|
||||
nodeList.append(node.to_dict())
|
||||
data['nodes'] = nodeList
|
||||
data['colliders'] = self.colliders.to_dict()
|
||||
return data
|
||||
Reference in New Issue
Block a user