Procedural Prefabs: Add PhysX mesh group support and example (#6073)

* Auto LOD script setup

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Working auto LODs

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Correctly selected LODs and added default prefab

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Cleanup code

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Cleanup code

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Add missing legal header, move name cleanup to scene_helpers, add documentation

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Add PhysX mesh group support.

Updated example script to show usage

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Add a physics collider component

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>

* Update DefaultOrValue call

Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>
monroegm-disable-blank-issue-2
amzn-mike 4 years ago committed by GitHub
parent 8186c268e3
commit 5f41463cd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,6 +8,7 @@
import azlmbr.bus
import azlmbr.math
from scene_api.scene_data import PrimitiveShape, DecompositionMode
from scene_helpers import *
@ -41,6 +42,35 @@ def add_material_component(entity_id):
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
def add_physx_meshes(scene_manifest: sceneData.SceneManifest, source_file_name: str, mesh_name_list: List, all_node_paths: List[str]):
first_mesh = mesh_name_list[0].get_path()
# Add a Box Primitive PhysX mesh with a comment
physx_box = scene_manifest.add_physx_primitive_mesh_group(source_file_name + "_box", PrimitiveShape.BOX, 0.0, None)
scene_manifest.physx_mesh_group_add_comment(physx_box, "This is a box primitive")
# Select the first mesh, unselect every other node
scene_manifest.physx_mesh_group_add_selected_node(physx_box, first_mesh)
for node in all_node_paths:
if node != first_mesh:
scene_manifest.physx_mesh_group_add_unselected_node(physx_box, node)
# Add a Convex Mesh PhysX mesh with a comment
convex_mesh = scene_manifest.add_physx_convex_mesh_group(source_file_name + "_convex", 0.08, .0004,
True, True, True, True, True, 24, True, "Glass")
scene_manifest.physx_mesh_group_add_comment(convex_mesh, "This is a convex mesh")
# Select/Unselect nodes using lists
all_except_first_mesh = [x for x in all_node_paths if x != first_mesh]
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(convex_mesh, [first_mesh], all_except_first_mesh)
# Configure mesh decomposition for this mesh
scene_manifest.physx_mesh_group_decompose_meshes(convex_mesh, 512, 32, .002, 100100, DecompositionMode.TETRAHEDRON,
0.06, 0.055, 0.00015, 3, 3, True, False)
# Add a Triangle mesh
triangle = scene_manifest.add_physx_triangle_mesh_group(source_file_name + "_triangle", False, True, True, True, True, True)
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(triangle, [first_mesh], all_except_first_mesh)
def update_manifest(scene):
import uuid, os
import azlmbr.scene.graph
@ -62,6 +92,8 @@ def update_manifest(scene):
previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
add_physx_meshes(scene_manifest, source_filename_only, mesh_name_list, all_node_paths)
# Loop every mesh node in the scene
for activeMeshIndex in range(len(mesh_name_list)):
mesh_name = mesh_name_list[activeMeshIndex]
@ -106,6 +138,26 @@ def update_manifest(scene):
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
# Add a physics component referencing the triangle mesh we made for the first node
if previous_entity_id is None:
physx_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "{FD429282-A075-4966-857F-D0BBF186CFE6} EditorColliderComponent")
json_update = json.dumps({
"ShapeConfiguration": {
"PhysicsAsset": {
"Asset": {
"assetHint": os.path.join(source_relative_path, source_filename_only + "_triangle.pxmesh")
}
}
}
})
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, physx_mesh_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for PhysX mesh component")
# an example of adding a material component to override the default material
if previous_entity_id is not None and first_mesh:
first_mesh = False

@ -4,8 +4,11 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import azlmbr.scene as sceneApi
import typing
import json
import azlmbr.scene as sceneApi
from enum import Enum, IntEnum
# Wraps the AZ.SceneAPI.Containers.SceneGraph.NodeIndex internal class
class SceneGraphNodeIndex:
@ -24,6 +27,7 @@ class SceneGraphNodeIndex:
def equal(self, other) -> bool:
return self.nodeIndex.Equal(other)
# Wraps AZ.SceneAPI.Containers.SceneGraph.Name internal class
class SceneGraphName():
def __init__(self, sceneGraphName) -> None:
@ -35,6 +39,7 @@ class SceneGraphName():
def get_name(self) -> str:
return self.name.GetName()
# Wraps AZ.SceneAPI.Containers.SceneGraph class
class SceneGraph():
def __init__(self, sceneGraphInstance) -> None:
@ -90,13 +95,26 @@ class SceneGraph():
def get_node_content(self, node):
return self.sceneGraph.GetNodeContent(node)
class PrimitiveShape(IntEnum):
BEST_FIT = 0
SPHERE = 1
BOX = 2
CAPSULE = 3
class DecompositionMode(IntEnum):
VOXEL = 0
TETRAHEDRON = 1
# Contains a dictionary to contain and export AZ.SceneAPI.Containers.SceneManifest
class SceneManifest():
def __init__(self):
self.manifest = {'values': []}
def add_mesh_group(self, name: str) -> dict:
meshGroup = {}
meshGroup = {}
meshGroup['$type'] = '{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup'
meshGroup['name'] = name
meshGroup['nodeSelectionList'] = {'selectedNodes': [], 'unselectedNodes': []}
@ -272,5 +290,242 @@ class SceneManifest():
mesh_group['rules']['rules'].append(rule)
def __add_physx_base_mesh_group(self, name: str, physics_material: typing.Optional[str]) -> dict:
import azlmbr.math
group = {
'$type': '{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup',
'id': azlmbr.math.Uuid_CreateRandom().ToString(),
'name': name,
'NodeSelectionList': {
'selectedNodes': [],
'unselectedNodes': []
},
"MaterialSlots": [
"Material"
],
"PhysicsMaterials": [
self.__default_or_value(physics_material, "<Default Physics Material>")
],
"rules": {
"rules": []
}
}
self.manifest['values'].append(group)
return group
def add_physx_triangle_mesh_group(self, name: str, merge_meshes: bool = True, weld_vertices: bool = False,
disable_clean_mesh: bool = False,
force_32bit_indices: bool = False,
suppress_triangle_mesh_remap_table: bool = False,
build_triangle_adjacencies: bool = False,
mesh_weld_tolerance: float = 0.0,
num_tris_per_leaf: int = 4,
physics_material: typing.Optional[str] = None) -> dict:
"""
Adds a Triangle type PhysX Mesh Group to the scene.
:param name: Name of the mesh group.
:param merge_meshes: When true, all selected nodes will be merged into a single collision mesh.
:param weld_vertices: When true, mesh welding is performed. Clean mesh must be enabled.
:param disable_clean_mesh: When true, mesh cleaning is disabled. This makes cooking faster.
:param force_32bit_indices: When true, 32-bit indices will always be created regardless of triangle count.
:param suppress_triangle_mesh_remap_table: When true, the face remap table is not created.
This saves a significant amount of memory, but the SDK will not be able to provide the remap
information for internal mesh triangles returned by collisions, sweeps or raycasts hits.
:param build_triangle_adjacencies: When true, the triangle adjacency information is created.
:param mesh_weld_tolerance: If mesh welding is enabled, this controls the distance at
which vertices are welded. If mesh welding is not enabled, this value defines the
acceptance distance for mesh validation. Provided no two vertices are within this
distance, the mesh is considered to be clean. If not, a warning will be emitted.
:param num_tris_per_leaf: Mesh cooking hint for max triangles per leaf limit. Fewer triangles per leaf
produces larger meshes with better runtime performance and worse cooking performance.
:param physics_material: Configure which physics material to use.
:return: The newly created mesh group.
"""
group = self.__add_physx_base_mesh_group(name, physics_material)
group["export method"] = 0
group["TriangleMeshAssetParams"] = {
"MergeMeshes": merge_meshes,
"WeldVertices": weld_vertices,
"DisableCleanMesh": disable_clean_mesh,
"Force32BitIndices": force_32bit_indices,
"SuppressTriangleMeshRemapTable": suppress_triangle_mesh_remap_table,
"BuildTriangleAdjacencies": build_triangle_adjacencies,
"MeshWeldTolerance": mesh_weld_tolerance,
"NumTrisPerLeaf": num_tris_per_leaf
}
return group
def add_physx_convex_mesh_group(self, name: str, area_test_epsilon: float = 0.059, plane_tolerance: float = 0.0006,
use_16bit_indices: bool = False,
check_zero_area_triangles: bool = False,
quantize_input: bool = False,
use_plane_shifting: bool = False,
shift_vertices: bool = False,
gauss_map_limit: int = 32,
build_gpu_data: bool = False,
physics_material: typing.Optional[str] = None) -> dict:
"""
Adds a Convex type PhysX Mesh Group to the scene.
:param name: Name of the mesh group.
:param area_test_epsilon: If the area of a triangle of the hull is below this value, the triangle will be
rejected. This test is done only if Check Zero Area Triangles is used.
:param plane_tolerance: The value is used during hull construction. When a new point is about to be added
to the hull it gets dropped when the point is closer to the hull than the planeTolerance.
:param use_16bit_indices: Denotes the use of 16-bit vertex indices in Convex triangles or polygons.
:param check_zero_area_triangles: Checks and removes almost zero-area triangles during convex hull computation.
The rejected area size is specified in Area Test Epsilon.
:param quantize_input: Quantizes the input vertices using the k-means clustering.
:param use_plane_shifting: Enables plane shifting vertex limit algorithm. Plane shifting is an alternative
algorithm for the case when the computed hull has more vertices than the specified vertex
limit.
:param shift_vertices: Convex hull input vertices are shifted to be around origin to provide better
computation stability
:param gauss_map_limit: Vertex limit beyond which additional acceleration structures are computed for each
convex mesh. Increase that limit to reduce memory usage. Computing the extra structures
all the time does not guarantee optimal performance.
:param build_gpu_data: When true, additional information required for GPU-accelerated rigid body
simulation is created. This can increase memory usage and cooking times for convex meshes
and triangle meshes. Convex hulls are created with respect to GPU simulation limitations.
Vertex limit is set to 64 and vertex limit per face is internally set to 32.
:param physics_material: Configure which physics material to use.
:return: The newly created mesh group.
"""
group = self.__add_physx_base_mesh_group(name, physics_material)
group["export method"] = 1
group["ConvexAssetParams"] = {
"AreaTestEpsilon": area_test_epsilon,
"PlaneTolerance": plane_tolerance,
"Use16bitIndices": use_16bit_indices,
"CheckZeroAreaTriangles": check_zero_area_triangles,
"QuantizeInput": quantize_input,
"UsePlaneShifting": use_plane_shifting,
"ShiftVertices": shift_vertices,
"GaussMapLimit": gauss_map_limit,
"BuildGpuData": build_gpu_data
}
return group
def add_physx_primitive_mesh_group(self, name: str,
primitive_shape_target: PrimitiveShape = PrimitiveShape.BEST_FIT,
volume_term_coefficient: float = 0.0,
physics_material: typing.Optional[str] = None) -> dict:
"""
Adds a Primitive Shape type PhysX Mesh Group to the scene
:param name: Name of the mesh group.
:param primitive_shape_target: The shape that should be fitted to this mesh. If BEST_FIT is selected, the
algorithm will determine which of the shapes fits best.
:param volume_term_coefficient: This parameter controls how aggressively the primitive fitting algorithm will try
to minimize the volume of the fitted primitive. A value of 0 (no volume minimization) is
recommended for most meshes, especially those with moderate to high vertex counts.
:param physics_material: Configure which physics material to use.
:return: The newly created mesh group.
"""
group = self.__add_physx_base_mesh_group(name, physics_material)
group["export method"] = 2
group["PrimitiveAssetParams"] = {
"PrimitiveShapeTarget": int(primitive_shape_target),
"VolumeTermCoefficient": volume_term_coefficient
}
return group
def physx_mesh_group_decompose_meshes(self, mesh_group: dict, max_convex_hulls: int = 1024,
max_num_vertices_per_convex_hull: int = 64,
concavity: float = .001,
resolution: float = 100000,
mode: DecompositionMode = DecompositionMode.VOXEL,
alpha: float = .05,
beta: float = .05,
min_volume_per_convex_hull: float = 0.0001,
plane_downsampling: int = 4,
convex_hull_downsampling: int = 4,
pca: bool = False,
project_hull_vertices: bool = True) -> None:
"""
Enables and configures mesh decomposition for a PhysX Mesh Group.
Only valid for convex or primitive mesh types.
:param mesh_group: Mesh group to configure decomposition for.
:param max_convex_hulls: Controls the maximum number of hulls to generate.
:param max_num_vertices_per_convex_hull: Controls the maximum number of triangles per convex hull.
:param concavity: Maximum concavity of each approximate convex hull.
:param resolution: Maximum number of voxels generated during the voxelization stage.
:param mode: Select voxel-based approximate convex decomposition or tetrahedron-based
approximate convex decomposition.
:param alpha: Controls the bias toward clipping along symmetry planes.
:param beta: Controls the bias toward clipping along revolution axes.
:param min_volume_per_convex_hull: Controls the adaptive sampling of the generated convex hulls.
:param plane_downsampling: Controls the granularity of the search for the best clipping plane.
:param convex_hull_downsampling: Controls the precision of the convex hull generation process
during the clipping plane selection stage.
:param pca: Enable or disable normalizing the mesh before applying the convex decomposition.
:param project_hull_vertices: Project the output convex hull vertices onto the original source mesh to increase
the floating point accuracy of the results.
"""
mesh_group['DecomposeMeshes'] = True
mesh_group['ConvexDecompositionParams'] = {
"MaxConvexHulls": max_convex_hulls,
"MaxNumVerticesPerConvexHull": max_num_vertices_per_convex_hull,
"Concavity": concavity,
"Resolution": resolution,
"Mode": int(mode),
"Alpha": alpha,
"Beta": beta,
"MinVolumePerConvexHull": min_volume_per_convex_hull,
"PlaneDownsampling": plane_downsampling,
"ConvexHullDownsampling": convex_hull_downsampling,
"PCA": pca,
"ProjectHullVertices": project_hull_vertices
}
def physx_mesh_group_add_selected_node(self, mesh_group: dict, node: str) -> None:
"""
Adds a node to the selected nodes list
:param mesh_group: Mesh group to add to.
:param node: Node path to add.
"""
mesh_group['NodeSelectionList']['selectedNodes'].append(node)
def physx_mesh_group_add_unselected_node(self, mesh_group: dict, node: str) -> None:
"""
Adds a node to the unselected nodes list
:param mesh_group: Mesh group to add to.
:param node: Node path to add.
"""
mesh_group['NodeSelectionList']['unselectedNodes'].append(node)
def physx_mesh_group_add_selected_unselected_nodes(self, mesh_group: dict, selected: typing.List[str],
unselected: typing.List[str]) -> None:
"""
Adds a set of nodes to the selected/unselected node lists
:param mesh_group: Mesh group to add to.
:param selected: List of node paths to add to the selected list.
:param unselected: List of node paths to add to the unselected list.
"""
mesh_group['NodeSelectionList']['selectedNodes'].extend(selected)
mesh_group['NodeSelectionList']['unselectedNodes'].extend(unselected)
def physx_mesh_group_add_comment(self, mesh_group: dict, comment: str) -> None:
"""
Adds a comment rule
:param mesh_group: Mesh group to add the rule to.
:param comment: Comment string.
"""
rule = {
"$type": "CommentRule",
"comment": comment
}
mesh_group['rules']['rules'].append(rule)
def export(self):
return json.dumps(self.manifest)

Loading…
Cancel
Save