diff --git a/dev/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp b/dev/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp index 9e050aaf28..46a4c9a3f8 100644 --- a/dev/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp +++ b/dev/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp @@ -78,7 +78,6 @@ namespace PhysX ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorShapeColliderComponent::m_subdivisionCount, "Subdivision count", "Number of angular subdivisions in the PhysX cylinder") ->Attribute(AZ::Edit::Attributes::Min, Utils::MinFrustumSubdivisions) - ->Attribute(AZ::Edit::Attributes::Max, Utils::MaxFrustumSubdivisions) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorShapeColliderComponent::OnSubdivisionCountChange) ->Attribute(AZ::Edit::Attributes::Visibility, &EditorShapeColliderComponent::SubdivisionCountVisibility) ; @@ -421,34 +420,72 @@ namespace PhysX return; } - AZStd::optional> points = Utils::CreatePointsAtFrustumExtents(height, radius, radius, m_subdivisionCount); + AZStd::optional> mbPoints = Utils::CreatePointsAtFrustumExtents(height, radius, radius, m_subdivisionCount); - if (!points.has_value()) + if (!mbPoints.has_value()) { AZ_Warning("PhysX", false, "Could not generate cylinder shape collider."); return; } + const auto& points = mbPoints.value(); - AZStd::optional shapeConfig = Utils::CreatePxCookedMeshConfiguration(points.value(), scale); - - if (shapeConfig.has_value()) + // Temporary to hold new shape configs, in case anything below fails, keep the old shape configs + decltype(m_shapeConfigs) shapeConfigs; + const auto AddMesh = [&shapeConfigs](const AZStd::vector& pts) { - if (m_shapeType != ShapeType::Cylinder) + if (AZStd::optional shapeConfig = Utils::CreatePxCookedMeshConfiguration(pts, AZ::Vector3::CreateOne())) { - m_shapeConfigs.clear(); - m_shapeConfigs.push_back(AZStd::make_shared(shapeConfig.value())); - - m_shapeType = ShapeType::Cylinder; + shapeConfigs.push_back(AZStd::make_shared(shapeConfig.value())); + return true; } else { - Physics::CookedMeshShapeConfiguration& configuration = - static_cast(*m_shapeConfigs.back()); - configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value()); + AZ_Warning("PhysX", false, "Could not generate cylinder shape collider."); + return false; } + }; - CreateStaticEditorCollider(); + // PhysX convex meshes are limited to 256 vertices + // Split meshes into approximately equal groups of this many vertices + static const size_t VertexLimit = 256; + const size_t totalVertices = points.size(); + const size_t nMeshes = (size_t)::ceilf((float)totalVertices / (float)VertexLimit); + if (nMeshes == 1) + { + if (!AddMesh(points)) return; } + else + { + // The number of vertices in each arc shape (splitting the cylinder into arcs of equal size) + const size_t groupSize = (size_t)::ceilf((float)totalVertices / (float)nMeshes); + + // The points for a regular polygon prism on the interior of the cylinder, whose number of sides is nMeshes + AZStd::vector insidePiece; + + // This handles outer sections of the cylinder, i.e. extrusions of arcs of the circle faces + for (size_t startVertex = 0; startVertex < totalVertices; startVertex += groupSize) + { + const size_t endVertex = AZ::GetMin(startVertex + groupSize, totalVertices); + const size_t nVertices = endVertex - startVertex; + AZ_Assert(startVertex + nVertices <= totalVertices, AZ_FUNCTION_SIGNATURE " - bad vertex index"); + + // Add mesh shape for this arc + if (!AddMesh(AZStd::vector(&points[startVertex], &points[startVertex] + nVertices))) return; + + // Add the first and last edge of the cylinder arc + insidePiece.push_back(points[startVertex]); + insidePiece.push_back(points[startVertex+1]); + insidePiece.push_back(points[startVertex+nVertices-1]); + insidePiece.push_back(points[startVertex+nVertices-2]); + } + + // This handles the regular polygonal prism on the interior + if (!AddMesh(insidePiece)) return; + } + + m_shapeConfigs.swap(shapeConfigs); + m_shapeType = ShapeType::Cylinder; + CreateStaticEditorCollider(); } AZ::u32 EditorShapeColliderComponent::OnSubdivisionCountChange() @@ -740,13 +777,13 @@ namespace PhysX else if (m_shapeType == ShapeType::Cylinder) { - if (!m_shapeConfigs.empty()) + AZ::u32 shapeIndex = 0; + for (const auto shapeConfig : m_shapeConfigs) { - const AZ::u32 shapeIndex = 0; const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId()); - Physics::ShapeConfiguration* shapeConfig = m_shapeConfigs[0].get(); m_colliderDebugDraw.BuildMeshes(*shapeConfig, shapeIndex); - m_colliderDebugDraw.DrawMesh(debugDisplay, m_colliderConfig, *static_cast(shapeConfig), uniformScale, shapeIndex); + m_colliderDebugDraw.DrawMesh(debugDisplay, m_colliderConfig, *static_cast(shapeConfig.get()), uniformScale, shapeIndex); + ++shapeIndex; } } diff --git a/dev/Gems/PhysX/Code/Source/Utils.cpp b/dev/Gems/PhysX/Code/Source/Utils.cpp index 865778dfd7..53ddbb1e31 100644 --- a/dev/Gems/PhysX/Code/Source/Utils.cpp +++ b/dev/Gems/PhysX/Code/Source/Utils.cpp @@ -288,9 +288,9 @@ namespace PhysX return {}; } - if (subdivisions < MinFrustumSubdivisions || subdivisions > MaxFrustumSubdivisions) + if (subdivisions < MinFrustumSubdivisions) { - AZ_Error("PhysX", false, "Frustum subdivision count %u is not in [%u, %u] range", subdivisions, MinFrustumSubdivisions, MaxFrustumSubdivisions); + AZ_Error("PhysX", false, "Frustum subdivision count %u is not at least %u", subdivisions, MinFrustumSubdivisions); return {}; }