diff --git a/PlayRho/Collision/Shapes/ChainShapeConf.cpp b/PlayRho/Collision/Shapes/ChainShapeConf.cpp index c1ae24db15..ec628f0894 100644 --- a/PlayRho/Collision/Shapes/ChainShapeConf.cpp +++ b/PlayRho/Collision/Shapes/ChainShapeConf.cpp @@ -43,11 +43,7 @@ namespace { #endif } // anonymous namespace -ChainShapeConf::ChainShapeConf(): - ShapeBuilder{ShapeConf{ShapeConf{}.UseVertexRadius(GetDefaultVertexRadius())}} -{ - // Intentionally empty. -} +ChainShapeConf::ChainShapeConf() = default; ChainShapeConf& ChainShapeConf::Set(std::vector vertices) { @@ -104,7 +100,6 @@ MassData ChainShapeConf::GetMassData() const noexcept const auto density = this->density; if (density > AreaDensity(0)) { - const auto vertexRadius = this->vertexRadius; const auto vertexCount = GetVertexCount(); if (vertexCount > 1) { @@ -147,7 +142,6 @@ DistanceProxy ChainShapeConf::GetChild(ChildCounter index) const { throw InvalidArgument("index out of range"); } - const auto vertexRadius = this->vertexRadius; const auto vertexCount = GetVertexCount(); if (vertexCount > 1) { diff --git a/PlayRho/Collision/Shapes/ChainShapeConf.hpp b/PlayRho/Collision/Shapes/ChainShapeConf.hpp index b0eb941158..0e3a19fb9b 100644 --- a/PlayRho/Collision/Shapes/ChainShapeConf.hpp +++ b/PlayRho/Collision/Shapes/ChainShapeConf.hpp @@ -51,7 +51,7 @@ class ChainShapeConf: public ShapeBuilder /// @brief Gets the default vertex radius. static PLAYRHO_CONSTEXPR inline NonNegative GetDefaultVertexRadius() noexcept { - return DefaultLinearSlop * 2; + return NonNegative{DefaultLinearSlop * Real{2}}; } /// @brief Default constructor. @@ -77,6 +77,9 @@ class ChainShapeConf: public ShapeBuilder /// @brief Gets the mass data. MassData GetMassData() const noexcept; + /// @brief Uses the given vertex radius. + inline ChainShapeConf& UseVertexRadius(NonNegative value) noexcept; + /// @brief Gets the vertex count. ChildCounter GetVertexCount() const noexcept { @@ -112,11 +115,30 @@ class ChainShapeConf: public ShapeBuilder return !(lhs == rhs); } + /// @brief Vertex radius. + /// + /// @details This is the radius from the vertex that the shape's "skin" should + /// extend outward by. While any edges — line segments between multiple + /// vertices — are straight, corners between them (the vertices) are + /// rounded and treated as rounded. Shapes with larger vertex radiuses compared + /// to edge lengths therefore will be more prone to rolling or having other + /// shapes more prone to roll off of them. + /// + /// @note This should be a non-negative value. + /// + NonNegative vertexRadius = GetDefaultVertexRadius(); + private: std::vector m_vertices; ///< Vertices. std::vector m_normals; ///< Normals. }; +inline ChainShapeConf& ChainShapeConf::UseVertexRadius(NonNegative value) noexcept +{ + vertexRadius = value; + return *this; +} + // Free functions... /// @brief Gets the child count for a given chain shape configuration. @@ -150,6 +172,18 @@ inline ChildCounter GetNextIndex(const ChainShapeConf& shape, ChildCounter index return GetModuloNext(index, shape.GetVertexCount()); } +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const ChainShapeConf& arg) +{ + return arg.vertexRadius; +} + +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const ChainShapeConf& arg, ChildCounter) +{ + return GetVertexRadius(arg); +} + } // namespace d2 } // namespace playrho diff --git a/PlayRho/Collision/Shapes/DiskShapeConf.hpp b/PlayRho/Collision/Shapes/DiskShapeConf.hpp index a95cc287a2..9506208c13 100644 --- a/PlayRho/Collision/Shapes/DiskShapeConf.hpp +++ b/PlayRho/Collision/Shapes/DiskShapeConf.hpp @@ -41,18 +41,15 @@ namespace d2 { struct DiskShapeConf: ShapeBuilder { /// @brief Gets the default radius. - static PLAYRHO_CONSTEXPR inline Length GetDefaultRadius() noexcept + static PLAYRHO_CONSTEXPR inline NonNegative GetDefaultRadius() noexcept { - return DefaultLinearSlop * 2; + return NonNegative{DefaultLinearSlop * 2}; } - PLAYRHO_CONSTEXPR inline DiskShapeConf(): ShapeBuilder{ShapeConf{}.UseVertexRadius(GetDefaultRadius())} - { - // Intentionally empty. - } + PLAYRHO_CONSTEXPR DiskShapeConf() = default; /// @brief Initializing constructor. - PLAYRHO_CONSTEXPR inline DiskShapeConf(Length radius): ShapeBuilder{ShapeConf{}.UseVertexRadius(radius)} + PLAYRHO_CONSTEXPR inline DiskShapeConf(NonNegative r): vertexRadius{r} { // Intentionally empty. } @@ -65,9 +62,9 @@ struct DiskShapeConf: ShapeBuilder } /// @brief Uses the given value as the radius. - PLAYRHO_CONSTEXPR inline DiskShapeConf& UseRadius(Length radius) noexcept + PLAYRHO_CONSTEXPR inline DiskShapeConf& UseRadius(NonNegative r) noexcept { - vertexRadius = radius; + vertexRadius = r; return *this; } @@ -83,6 +80,19 @@ struct DiskShapeConf: ShapeBuilder return location; } + /// @brief Vertex radius. + /// + /// @details This is the radius from the vertex that the shape's "skin" should + /// extend outward by. While any edges — line segments between multiple + /// vertices — are straight, corners between them (the vertices) are + /// rounded and treated as rounded. Shapes with larger vertex radiuses compared + /// to edge lengths therefore will be more prone to rolling or having other + /// shapes more prone to roll off of them. + /// + /// @note This should be a non-negative value. + /// + NonNegative vertexRadius = GetDefaultRadius(); + /// @brief Location for the disk shape to be centered at. Length2 location = Length2{}; }; @@ -119,6 +129,19 @@ inline DistanceProxy GetChild(const DiskShapeConf& arg, ChildCounter index) return DistanceProxy{arg.vertexRadius, 1, &arg.location, nullptr}; } +/// @brief Gets the vertex radius of the given shape configuration. +PLAYRHO_CONSTEXPR inline NonNegative GetVertexRadius(const DiskShapeConf& arg) noexcept +{ + return arg.vertexRadius; +} + +/// @brief Gets the vertex radius of the given shape configuration. +PLAYRHO_CONSTEXPR inline NonNegative GetVertexRadius(const DiskShapeConf& arg, + ChildCounter) noexcept +{ + return GetVertexRadius(arg); +} + /// @brief Gets the mass data of the given disk shape configuration. inline MassData GetMassData(const DiskShapeConf& arg) noexcept { diff --git a/PlayRho/Collision/Shapes/EdgeShapeConf.cpp b/PlayRho/Collision/Shapes/EdgeShapeConf.cpp index aac0957e3e..bb98a48011 100644 --- a/PlayRho/Collision/Shapes/EdgeShapeConf.cpp +++ b/PlayRho/Collision/Shapes/EdgeShapeConf.cpp @@ -22,14 +22,8 @@ namespace playrho { namespace d2 { -EdgeShapeConf::EdgeShapeConf(): - ShapeBuilder{ShapeConf{}.UseVertexRadius(GetDefaultVertexRadius())} -{ - // Intentionally empty. -} - EdgeShapeConf::EdgeShapeConf(Length2 vA, Length2 vB, const EdgeShapeConf& conf) noexcept: - ShapeBuilder{conf}, m_vertices{vA, vB} + ShapeBuilder{conf}, vertexRadius{conf.vertexRadius}, m_vertices{vA, vB} { const auto normal = GetUnitVector(GetFwdPerpendicular(vB - vA)); m_normals[0] = normal; diff --git a/PlayRho/Collision/Shapes/EdgeShapeConf.hpp b/PlayRho/Collision/Shapes/EdgeShapeConf.hpp index 0b155d53d3..f64f2eaad3 100644 --- a/PlayRho/Collision/Shapes/EdgeShapeConf.hpp +++ b/PlayRho/Collision/Shapes/EdgeShapeConf.hpp @@ -42,9 +42,9 @@ class EdgeShapeConf: public ShapeBuilder { public: /// @brief Gets the default vertex radius. - static PLAYRHO_CONSTEXPR inline Length GetDefaultVertexRadius() noexcept + static PLAYRHO_CONSTEXPR inline NonNegative GetDefaultVertexRadius() noexcept { - return DefaultLinearSlop * Real{2}; + return NonNegative{DefaultLinearSlop * Real{2}}; } /// @brief Gets the default configuration. @@ -53,7 +53,7 @@ class EdgeShapeConf: public ShapeBuilder return EdgeShapeConf{}; } - EdgeShapeConf(); + EdgeShapeConf() = default; /// @brief Initializing constructor. EdgeShapeConf(Length2 vA, Length2 vB, const EdgeShapeConf& conf = GetDefaultConf()) noexcept; @@ -61,6 +61,9 @@ class EdgeShapeConf: public ShapeBuilder /// @brief Sets both vertices in one call. EdgeShapeConf& Set(Length2 vA, Length2 vB) noexcept; + /// @brief Uses the given vertex radius. + EdgeShapeConf& UseVertexRadius(NonNegative value) noexcept; + /// @brief Gets vertex A. Length2 GetVertexA() const noexcept { @@ -79,11 +82,30 @@ class EdgeShapeConf: public ShapeBuilder return DistanceProxy{vertexRadius, 2, m_vertices, m_normals}; } + /// @brief Vertex radius. + /// + /// @details This is the radius from the vertex that the shape's "skin" should + /// extend outward by. While any edges — line segments between multiple + /// vertices — are straight, corners between them (the vertices) are + /// rounded and treated as rounded. Shapes with larger vertex radiuses compared + /// to edge lengths therefore will be more prone to rolling or having other + /// shapes more prone to roll off of them. + /// + /// @note This should be a non-negative value. + /// + NonNegative vertexRadius = GetDefaultVertexRadius(); + private: Length2 m_vertices[2] = {Length2{}, Length2{}}; ///< Vertices UnitVec m_normals[2] = {UnitVec{}, UnitVec{}}; ///< Normals. }; +inline EdgeShapeConf& EdgeShapeConf::UseVertexRadius(NonNegative value) noexcept +{ + vertexRadius = value; + return *this; +} + // Free functions... /// @brief Equality operator. @@ -117,6 +139,18 @@ inline DistanceProxy GetChild(const EdgeShapeConf& arg, ChildCounter index) return arg.GetChild(); } +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const EdgeShapeConf& arg) noexcept +{ + return arg.vertexRadius; +} + +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const EdgeShapeConf& arg, ChildCounter) noexcept +{ + return GetVertexRadius(arg); +} + /// @brief Gets the mass data for the given shape configuration. inline MassData GetMassData(const EdgeShapeConf& arg) noexcept { diff --git a/PlayRho/Collision/Shapes/MultiShapeConf.cpp b/PlayRho/Collision/Shapes/MultiShapeConf.cpp index 5ea8fb64a4..57d1421cdc 100644 --- a/PlayRho/Collision/Shapes/MultiShapeConf.cpp +++ b/PlayRho/Collision/Shapes/MultiShapeConf.cpp @@ -34,13 +34,12 @@ MassData GetMassData(const MultiShapeConf& arg) noexcept const auto origin = Length2{}; auto weightedCenter = origin * Kilogram; auto I = RotInertia(0); - const auto vertexRadius = arg.vertexRadius; const auto density = arg.density; std::for_each(std::begin(arg.children), std::end(arg.children), [&](const ConvexHull& ch) { - const auto dp = ch.GetDistanceProxy(vertexRadius); - const auto md = playrho::d2::GetMassData(vertexRadius, density, + const auto dp = ch.GetDistanceProxy(); + const auto md = playrho::d2::GetMassData(ch.GetVertexRadius(), density, Span(dp.GetVertices().begin(), dp.GetVertexCount())); mass += Mass{md.mass}; weightedCenter += md.center * Mass{md.mass}; @@ -51,7 +50,7 @@ MassData GetMassData(const MultiShapeConf& arg) noexcept return MassData{center, mass, I}; } -ConvexHull ConvexHull::Get(const VertexSet& pointSet) +ConvexHull ConvexHull::Get(const VertexSet& pointSet, NonNegative vertexRadius) { auto vertices = GetConvexHullAsVector(pointSet); assert(!vertices.empty() && vertices.size() < std::numeric_limits::max()); @@ -74,12 +73,13 @@ ConvexHull ConvexHull::Get(const VertexSet& pointSet) normals.push_back(UnitVec{}); } - return ConvexHull{vertices, normals}; + return ConvexHull{vertices, normals, vertexRadius}; } -MultiShapeConf& MultiShapeConf::AddConvexHull(const VertexSet& pointSet) noexcept +MultiShapeConf& MultiShapeConf::AddConvexHull(const VertexSet& pointSet, + NonNegative vertexRadius) noexcept { - children.emplace_back(ConvexHull::Get(pointSet)); + children.emplace_back(ConvexHull::Get(pointSet, vertexRadius)); return *this; } diff --git a/PlayRho/Collision/Shapes/MultiShapeConf.hpp b/PlayRho/Collision/Shapes/MultiShapeConf.hpp index 61344e485d..1b8b87ff91 100644 --- a/PlayRho/Collision/Shapes/MultiShapeConf.hpp +++ b/PlayRho/Collision/Shapes/MultiShapeConf.hpp @@ -38,10 +38,11 @@ class ConvexHull public: /// @brief Gets the convex hull for the given set of vertices. - static ConvexHull Get(const VertexSet& pointSet); + static ConvexHull Get(const VertexSet& pointSet, NonNegative vertexRadius = + NonNegative{DefaultLinearSlop * Real{2}}); /// @brief Gets the distance proxy for this convex hull. - DistanceProxy GetDistanceProxy(Length vertexRadius) const + DistanceProxy GetDistanceProxy() const { return DistanceProxy{ vertexRadius, static_cast(vertices.size()), @@ -49,11 +50,18 @@ class ConvexHull }; } + /// @brief Gets the vertex radius of this convex hull. + /// @return Non-negative distance. + NonNegative GetVertexRadius() const noexcept + { + return vertexRadius; + } + /// @brief Equality operator. friend bool operator== (const ConvexHull& lhs, const ConvexHull& rhs) noexcept { - // Only need to check vertices are same since normals are calculated based on them. - return lhs.vertices == rhs.vertices; + // No need to check normals - they're based on vertices. + return lhs.vertexRadius == rhs.vertexRadius && lhs.vertices == rhs.vertices; } /// @brief Inequality operator. @@ -64,8 +72,9 @@ class ConvexHull private: /// @brief Initializing constructor. - ConvexHull(std::vector verts, std::vector norms): - vertices{verts}, normals{norms} + ConvexHull(std::vector verts, std::vector norms, + NonNegative vr): + vertices{verts}, normals{norms}, vertexRadius{vr} {} /// Array of vertices. @@ -77,6 +86,19 @@ class ConvexHull /// These are 90-degree clockwise-rotated unit-vectors of the vectors defined by /// consecutive pairs of elements of vertices. std::vector normals; + + /// @brief Vertex radius. + /// + /// @details This is the radius from the vertex that the shape's "skin" should + /// extend outward by. While any edges — line segments between multiple + /// vertices — are straight, corners between them (the vertices) are + /// rounded and treated as rounded. Shapes with larger vertex radiuses compared + /// to edge lengths therefore will be more prone to rolling or having other + /// shapes more prone to roll off of them. + /// + /// @note This should be a non-negative value. + /// + NonNegative vertexRadius; }; /// @brief The "multi-shape" shape configuration. @@ -85,9 +107,9 @@ class ConvexHull struct MultiShapeConf: public ShapeBuilder { /// @brief Gets the default vertex radius for the MultiShapeConf. - static PLAYRHO_CONSTEXPR inline Length GetDefaultVertexRadius() noexcept + static PLAYRHO_CONSTEXPR inline NonNegative GetDefaultVertexRadius() noexcept { - return DefaultLinearSlop * 2; + return NonNegative{DefaultLinearSlop * 2}; } /// @brief Gets the default configuration for a MultiShapeConf. @@ -97,7 +119,7 @@ struct MultiShapeConf: public ShapeBuilder } inline MultiShapeConf(): - ShapeBuilder{ShapeConf{}.UseVertexRadius(GetDefaultVertexRadius())} + ShapeBuilder{ShapeConf{}} { // Intentionally empty. } @@ -107,7 +129,8 @@ struct MultiShapeConf: public ShapeBuilder /// @warning the points may be re-ordered, even if they form a convex polygon /// @warning collinear points are handled but not removed. Collinear points /// may lead to poor stacking behavior. - MultiShapeConf& AddConvexHull(const VertexSet& pointSet) noexcept; + MultiShapeConf& AddConvexHull(const VertexSet& pointSet, NonNegative vertexRadius = + GetDefaultVertexRadius()) noexcept; std::vector children; ///< Children. }; @@ -117,9 +140,8 @@ struct MultiShapeConf: public ShapeBuilder /// @brief Equality operator. inline bool operator== (const MultiShapeConf& lhs, const MultiShapeConf& rhs) noexcept { - return lhs.vertexRadius == rhs.vertexRadius && lhs.friction == rhs.friction - && lhs.restitution == rhs.restitution && lhs.density == rhs.density - && lhs.children == rhs.children; + return lhs.friction == rhs.friction && lhs.restitution == rhs.restitution + && lhs.density == rhs.density && lhs.children == rhs.children; } /// @brief Inequality operator. @@ -141,13 +163,22 @@ inline DistanceProxy GetChild(const MultiShapeConf& arg, ChildCounter index) { throw InvalidArgument("index out of range"); } - const auto& child = arg.children.at(index); - return child.GetDistanceProxy(arg.vertexRadius); + return arg.children[index].GetDistanceProxy(); } /// @brief Gets the mass data for the given shape configuration. MassData GetMassData(const MultiShapeConf& arg) noexcept; +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const MultiShapeConf& arg, ChildCounter index) +{ + if (index >= GetChildCount(arg)) + { + throw InvalidArgument("index out of range"); + } + return arg.children[index].GetVertexRadius(); +} + } // namespace d2 } // namespace playrho diff --git a/PlayRho/Collision/Shapes/PolygonShapeConf.cpp b/PlayRho/Collision/Shapes/PolygonShapeConf.cpp index 4a331b92fb..e375d6d059 100644 --- a/PlayRho/Collision/Shapes/PolygonShapeConf.cpp +++ b/PlayRho/Collision/Shapes/PolygonShapeConf.cpp @@ -23,11 +23,7 @@ namespace playrho { namespace d2 { -PolygonShapeConf::PolygonShapeConf(): - ShapeBuilder{ShapeConf{}.UseVertexRadius(GetDefaultVertexRadius())} -{ - // Intentionally empty. -} +PolygonShapeConf::PolygonShapeConf() = default; PolygonShapeConf::PolygonShapeConf(Length hx, Length hy, const PolygonShapeConf& conf) noexcept: diff --git a/PlayRho/Collision/Shapes/PolygonShapeConf.hpp b/PlayRho/Collision/Shapes/PolygonShapeConf.hpp index 2f4a7b1646..f49aef24f4 100644 --- a/PlayRho/Collision/Shapes/PolygonShapeConf.hpp +++ b/PlayRho/Collision/Shapes/PolygonShapeConf.hpp @@ -42,9 +42,9 @@ class PolygonShapeConf: public ShapeBuilder { public: /// @brief Gets the default vertex radius for the PolygonShapeConf. - static PLAYRHO_CONSTEXPR inline Length GetDefaultVertexRadius() noexcept + static PLAYRHO_CONSTEXPR inline NonNegative GetDefaultVertexRadius() noexcept { - return DefaultLinearSlop * 2; + return NonNegative{DefaultLinearSlop * 2}; } /// @brief Gets the default configuration for a PolygonShapeConf. @@ -67,6 +67,9 @@ class PolygonShapeConf: public ShapeBuilder explicit PolygonShapeConf(Span points, const PolygonShapeConf& conf = GetDefaultConf()) noexcept; + /// @brief Uses the given vertex radius. + PolygonShapeConf& UseVertexRadius(NonNegative value) noexcept; + /// @brief Uses the given vertices. PolygonShapeConf& UseVertices(const std::vector& verts) noexcept; @@ -154,6 +157,19 @@ class PolygonShapeConf: public ShapeBuilder /// @brief Gets the centroid. Length2 GetCentroid() const noexcept { return m_centroid; } + /// @brief Vertex radius. + /// + /// @details This is the radius from the vertex that the shape's "skin" should + /// extend outward by. While any edges — line segments between multiple + /// vertices — are straight, corners between them (the vertices) are + /// rounded and treated as rounded. Shapes with larger vertex radiuses compared + /// to edge lengths therefore will be more prone to rolling or having other + /// shapes more prone to roll off of them. + /// + /// @note This should be a non-negative value. + /// + NonNegative vertexRadius = GetDefaultVertexRadius(); + private: /// @brief Array of vertices. /// @details Consecutive vertices constitute "edges" of the polygon. @@ -168,6 +184,12 @@ class PolygonShapeConf: public ShapeBuilder Length2 m_centroid = GetInvalid(); }; +inline PolygonShapeConf& PolygonShapeConf::UseVertexRadius(NonNegative value) noexcept +{ + vertexRadius = value; + return *this; +} + // Free functions... /// @brief Gets the "child" count for the given shape configuration. @@ -188,6 +210,18 @@ inline DistanceProxy GetChild(const PolygonShapeConf& arg, ChildCounter index) arg.GetVertices().data(), arg.GetNormals().data()}; } +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const PolygonShapeConf& arg) noexcept +{ + return arg.vertexRadius; +} + +/// @brief Gets the vertex radius of the given shape configuration. +inline NonNegative GetVertexRadius(const PolygonShapeConf& arg, ChildCounter) noexcept +{ + return GetVertexRadius(arg); +} + /// @brief Gets the mass data for the given shape configuration. inline MassData GetMassData(const PolygonShapeConf& arg) noexcept { diff --git a/PlayRho/Collision/Shapes/Shape.cpp b/PlayRho/Collision/Shapes/Shape.cpp index 988bdc5be6..2e45c598d6 100644 --- a/PlayRho/Collision/Shapes/Shape.cpp +++ b/PlayRho/Collision/Shapes/Shape.cpp @@ -23,8 +23,58 @@ namespace playrho { namespace d2 { +namespace { -// Free functions... +struct DefaultShapeConf +{ +}; + +ChildCounter GetChildCount(const DefaultShapeConf&) noexcept +{ + return 0; +} + +DistanceProxy GetChild(const DefaultShapeConf&, ChildCounter) +{ + throw InvalidArgument("index out of range"); +} + +MassData GetMassData(const DefaultShapeConf&) noexcept +{ + return MassData{}; +} + +Real GetFriction(const DefaultShapeConf&) noexcept +{ + return Real{0}; +} + +Real GetRestitution(const DefaultShapeConf&) noexcept +{ + return Real{0}; +} + +NonNegative GetDensity(const DefaultShapeConf&) noexcept +{ + return NonNegative{0_kgpm2}; +} + +NonNegative GetVertexRadius(const DefaultShapeConf&, ChildCounter) +{ + throw InvalidArgument("index out of range"); +} + +constexpr bool operator== (const DefaultShapeConf&, const DefaultShapeConf&) noexcept +{ + return true; +} + +} // annonymous namespace + +Shape::Shape(): m_self{std::make_shared>(DefaultShapeConf{})} +{ + // Intentionally empty. +} bool TestPoint(const Shape& shape, Length2 point) noexcept { diff --git a/PlayRho/Collision/Shapes/Shape.hpp b/PlayRho/Collision/Shapes/Shape.hpp index 45f763a307..ffd5f4803d 100644 --- a/PlayRho/Collision/Shapes/Shape.hpp +++ b/PlayRho/Collision/Shapes/Shape.hpp @@ -69,7 +69,7 @@ Real GetRestitution(const Shape& shape) noexcept; /// @return Non-negative density (in mass per area). NonNegative GetDensity(const Shape& shape) noexcept; -/// @brief Gets the vertex radius of the given shape. +/// @brief Gets the vertex radius of the indexed child of the given shape. /// /// @details This gets the radius from the vertex that the shape's "skin" should /// extend outward by. While any edges - line segments between multiple vertices - @@ -79,13 +79,18 @@ NonNegative GetDensity(const Shape& shape) noexcept; /// to roll off of them. Here's an image of a shape configured via a /// PolygonShapeConf with it's skin drawn: /// +/// @param shape Shape to get child's vertex radius for. +/// @param idx Child index to get vertex radius for. +/// /// @image html SkinnedPolygon.png /// /// @note This must be a non-negative value. /// /// @sa UseVertexRadius /// -NonNegative GetVertexRadius(const Shape& shape) noexcept; +/// @throws InvalidArgument if the child index is not less than the child count. +/// +NonNegative GetVertexRadius(const Shape& shape, ChildCounter idx); /// @brief Visits the given shape with the potentially non-null user data pointer. /// @sa https://en.wikipedia.org/wiki/Visitor_pattern @@ -128,9 +133,10 @@ bool operator!= (const Shape& lhs, const Shape& rhs) noexcept; /// @brief Shape. /// -/// @details A shape is used for collision detection. You can create a shape however you like. -/// Shapes used for simulation in World are created automatically when a -/// Fixture is created. Shapes may encapsulate zero or more child shapes. +/// @details A shape is used for collision detection. You can create a shape from any +/// supporting type. Shapes are conceptually made up of zero or more convex child shapes +/// where each child shape is made up of zero or more vertices and an associated radius +/// called its "vertex radius". /// /// @note This class implements polymorphism without inheritance. This is based on a technique /// that's described by Sean Parent in his January 2017 Norwegian Developers Conference @@ -150,7 +156,8 @@ class Shape { public: /// @brief Default constructor. - Shape() = delete; + /// @throws std::bad_alloc if there's a failure allocating storage. + Shape(); /// @brief Initializing constructor. /// @param arg Configuration value to construct a shape instance for. @@ -163,6 +170,7 @@ class Shape /// @sa GetDensity /// @sa GetFriction /// @sa GetRestitution + /// @throws std::bad_alloc if there's a failure allocating storage. template explicit Shape(T arg): m_self{std::make_shared>(std::move(arg))} { @@ -196,9 +204,9 @@ class Shape return shape.m_self->GetMassData_(); } - friend NonNegative GetVertexRadius(const Shape& shape) noexcept + friend NonNegative GetVertexRadius(const Shape& shape, ChildCounter idx) { - return shape.m_self->GetVertexRadius_(); + return shape.m_self->GetVertexRadius_(idx); } friend Real GetFriction(const Shape& shape) noexcept @@ -265,7 +273,8 @@ class Shape virtual MassData GetMassData_() const noexcept = 0; /// @brief Gets the vertex radius. - virtual NonNegative GetVertexRadius_() const noexcept = 0; + /// @param idx Child index to get vertex radius for. + virtual NonNegative GetVertexRadius_(ChildCounter idx) const = 0; /// @brief Gets the density. virtual NonNegative GetDensity_() const noexcept = 0; @@ -328,9 +337,9 @@ class Shape return GetMassData(data); } - NonNegative GetVertexRadius_() const noexcept override + NonNegative GetVertexRadius_(ChildCounter idx) const override { - return GetVertexRadius(data); + return GetVertexRadius(data, idx); } NonNegative GetDensity_() const noexcept override diff --git a/PlayRho/Collision/Shapes/ShapeConf.hpp b/PlayRho/Collision/Shapes/ShapeConf.hpp index aba0969876..635dfed0f7 100644 --- a/PlayRho/Collision/Shapes/ShapeConf.hpp +++ b/PlayRho/Collision/Shapes/ShapeConf.hpp @@ -33,19 +33,6 @@ namespace d2 { /// @note This is a nested base value class for initializing shapes. struct BaseShapeConf { - /// @brief Vertex radius. - /// - /// @details This is the radius from the vertex that the shape's "skin" should - /// extend outward by. While any edges — line segments between multiple - /// vertices — are straight, corners between them (the vertices) are - /// rounded and treated as rounded. Shapes with larger vertex radiuses compared - /// to edge lengths therefore will be more prone to rolling or having other - /// shapes more prone to roll off of them. - /// - /// @note This should be a non-negative value. - /// - NonNegative vertexRadius = NonNegative{DefaultLinearSlop * Real{2}}; - /// @brief Friction coefficient. /// /// @note This must be a value between 0 and +infinity. It is safer however to @@ -92,9 +79,6 @@ struct ShapeBuilder: BaseShapeConf { // Intentionally empty. } - - /// @brief Uses the given vertex radius. - PLAYRHO_CONSTEXPR inline ConcreteConf& UseVertexRadius(NonNegative value) noexcept; /// @brief Uses the given friction. PLAYRHO_CONSTEXPR inline ConcreteConf& UseFriction(NonNegative value) noexcept; @@ -106,14 +90,6 @@ struct ShapeBuilder: BaseShapeConf PLAYRHO_CONSTEXPR inline ConcreteConf& UseDensity(NonNegative value) noexcept; }; -template -PLAYRHO_CONSTEXPR inline ConcreteConf& -ShapeBuilder::UseVertexRadius(NonNegative value) noexcept -{ - vertexRadius = value; - return static_cast(*this); -} - template PLAYRHO_CONSTEXPR inline ConcreteConf& ShapeBuilder::UseFriction(NonNegative value) noexcept @@ -146,12 +122,6 @@ struct ShapeConf: public ShapeBuilder // Free functions... -/// @brief Gets the vertex radius of the given shape configuration. -PLAYRHO_CONSTEXPR inline NonNegative GetVertexRadius(const BaseShapeConf& arg) noexcept -{ - return arg.vertexRadius; -} - /// @brief Gets the density of the given shape configuration. PLAYRHO_CONSTEXPR inline NonNegative GetDensity(const BaseShapeConf& arg) noexcept { diff --git a/PlayRho/Collision/WorldManifold.cpp b/PlayRho/Collision/WorldManifold.cpp index 9299a30231..e186c7ad67 100644 --- a/PlayRho/Collision/WorldManifold.cpp +++ b/PlayRho/Collision/WorldManifold.cpp @@ -126,12 +126,14 @@ WorldManifold GetWorldManifold(const Manifold& manifold, WorldManifold GetWorldManifold(const Contact& contact) { const auto fA = contact.GetFixtureA(); + const auto iA = contact.GetChildIndexA(); const auto xfA = GetTransformation(*fA); - const auto radiusA = GetVertexRadius(fA->GetShape()); + const auto radiusA = GetVertexRadius(fA->GetShape(), iA); const auto fB = contact.GetFixtureB(); + const auto iB = contact.GetChildIndexB(); const auto xfB = GetTransformation(*fB); - const auto radiusB = GetVertexRadius(fB->GetShape()); + const auto radiusB = GetVertexRadius(fB->GetShape(), iB); return GetWorldManifold(contact.GetManifold(), xfA, radiusA, xfB, radiusB); } diff --git a/PlayRho/Dynamics/Contacts/Contact.cpp b/PlayRho/Dynamics/Contacts/Contact.cpp index c4a1f8f070..68cb76d8f3 100644 --- a/PlayRho/Dynamics/Contacts/Contact.cpp +++ b/PlayRho/Dynamics/Contacts/Contact.cpp @@ -67,16 +67,6 @@ Contact::Contact(Fixture* fA, ChildCounter iA, Fixture* fB, ChildCounter iB): assert(fA->GetBody() != fB->GetBody()); } -ChildCounter Contact::GetChildIndexA() const noexcept -{ - return m_indexA; -} - -ChildCounter Contact::GetChildIndexB() const noexcept -{ - return m_indexB; -} - void Contact::Update(const UpdateConf& conf, ContactListener* listener) { const auto oldManifold = m_manifold; diff --git a/PlayRho/Dynamics/Contacts/Contact.hpp b/PlayRho/Dynamics/Contacts/Contact.hpp index fcf34d11cf..41eeffd149 100644 --- a/PlayRho/Dynamics/Contacts/Contact.hpp +++ b/PlayRho/Dynamics/Contacts/Contact.hpp @@ -510,6 +510,16 @@ inline void Contact::UnsetIslanded() noexcept m_flags &= ~e_islandFlag; } +inline ChildCounter Contact::GetChildIndexA() const noexcept +{ + return m_indexA; +} + +inline ChildCounter Contact::GetChildIndexB() const noexcept +{ + return m_indexB; +} + // Free functions... /// @brief Contact pointer type. @@ -537,6 +547,20 @@ inline Fixture* GetFixtureB(const Contact& contact) noexcept return contact.GetFixtureB(); } +/// @brief Gets the child index A of the given contact. +/// @relatedalso Contact +inline ChildCounter GetChildIndexA(const Contact& contact) noexcept +{ + return contact.GetChildIndexA(); +} + +/// @brief Gets the child index B of the given contact. +/// @relatedalso Contact +inline ChildCounter GetChildIndexB(const Contact& contact) noexcept +{ + return contact.GetChildIndexB(); +} + /// @brief Whether the given contact has a sensor. /// @relatedalso Contact bool HasSensor(const Contact& contact) noexcept; diff --git a/PlayRho/Dynamics/World.cpp b/PlayRho/Dynamics/World.cpp index b1ae46a537..b05a29a146 100644 --- a/PlayRho/Dynamics/World.cpp +++ b/PlayRho/Dynamics/World.cpp @@ -211,13 +211,14 @@ namespace { { auto constraints = PositionConstraints{}; constraints.reserve(contacts.size()); - transform(cbegin(contacts), cend(contacts), back_inserter(constraints), - [&](const Contact *contact) { + transform(cbegin(contacts), cend(contacts), back_inserter(constraints), [&](const Contact *contact) { const auto& manifold = static_cast(contact)->GetManifold(); const auto& fixtureA = *(GetFixtureA(*contact)); const auto& fixtureB = *(GetFixtureB(*contact)); - + const auto indexA = GetChildIndexA(*contact); + const auto indexB = GetChildIndexB(*contact); + const auto bodyA = GetBodyA(*contact); const auto shapeA = fixtureA.GetShape(); @@ -227,8 +228,8 @@ namespace { const auto bodyConstraintA = At(bodies, bodyA); const auto bodyConstraintB = At(bodies, bodyB); - const auto radiusA = GetVertexRadius(shapeA); - const auto radiusB = GetVertexRadius(shapeB); + const auto radiusA = GetVertexRadius(shapeA, indexA); + const auto radiusB = GetVertexRadius(shapeB, indexB); return PositionConstraint{ manifold, *bodyConstraintA, radiusA, *bodyConstraintB, radiusB @@ -250,15 +251,16 @@ namespace { { auto velConstraints = VelocityConstraints{}; velConstraints.reserve(contacts.size()); - transform(cbegin(contacts), cend(contacts), back_inserter(velConstraints), - [&](const ContactPtr& contact) { + transform(cbegin(contacts), cend(contacts), back_inserter(velConstraints), [&](const ContactPtr& contact) { const auto& manifold = contact->GetManifold(); const auto fixtureA = contact->GetFixtureA(); const auto fixtureB = contact->GetFixtureB(); const auto friction = contact->GetFriction(); const auto restitution = contact->GetRestitution(); const auto tangentSpeed = contact->GetTangentSpeed(); - + const auto indexA = GetChildIndexA(*contact); + const auto indexB = GetChildIndexB(*contact); + const auto bodyA = fixtureA->GetBody(); const auto shapeA = fixtureA->GetShape(); @@ -268,8 +270,8 @@ namespace { const auto bodyConstraintA = At(bodies, bodyA); const auto bodyConstraintB = At(bodies, bodyB); - const auto radiusA = GetVertexRadius(shapeA); - const auto radiusB = GetVertexRadius(shapeB); + const auto radiusA = GetVertexRadius(shapeA, indexA); + const auto radiusB = GetVertexRadius(shapeB, indexB); const auto xfA = GetTransformation(bodyConstraintA->GetPosition(), bodyConstraintA->GetLocalCenter()); @@ -2395,14 +2397,18 @@ Fixture* World::CreateFixture(Body& body, const Shape& shape, throw InvalidArgument("World::CreateFixture: invalid body"); } - const auto vr = GetVertexRadius(shape); - if (!(vr >= GetMinVertexRadius())) - { - throw InvalidArgument("World::CreateFixture: vertex radius < min"); - } - if (!(vr <= GetMaxVertexRadius())) + const auto childCount = GetChildCount(shape); + for (auto i = ChildCounter{0}; i < childCount; ++i) { - throw InvalidArgument("World::CreateFixture: vertex radius > max"); + const auto vr = GetVertexRadius(shape, i); + if (!(vr >= GetMinVertexRadius())) + { + throw InvalidArgument("World::CreateFixture: vertex radius < min"); + } + if (!(vr <= GetMaxVertexRadius())) + { + throw InvalidArgument("World::CreateFixture: vertex radius > max"); + } } if (IsLocked()) @@ -2694,15 +2700,16 @@ void SetAccelerations(World& world, LinearAcceleration2 acceleration) noexcept }); } -Body* CreateRectangularEnclosingBody(World& world, Length2 dimensions, const ShapeConf& baseConf) +Body* CreateRectangularEnclosingBody(World& world, Length2 dimensions, const ShapeConf& baseConf, + Length thickness) { const auto body = world.CreateBody(); auto conf = ChainShapeConf{}; conf.restitution = baseConf.restitution; - conf.vertexRadius = baseConf.vertexRadius; conf.friction = baseConf.friction; conf.density = baseConf.density; + conf.vertexRadius = thickness; const auto halfWidth = GetX(dimensions) / Real{2}; const auto halfHeight = GetY(dimensions) / Real{2}; diff --git a/PlayRho/Dynamics/World.hpp b/PlayRho/Dynamics/World.hpp index 291d10d700..c34eba0e33 100644 --- a/PlayRho/Dynamics/World.hpp +++ b/PlayRho/Dynamics/World.hpp @@ -1063,13 +1063,14 @@ inline void ClearForces(World& world) noexcept /// @brief Creates a rectangular enclosure. /// @relatedalso World Body* CreateRectangularEnclosingBody(World& world, Length2 dimensions, - const ShapeConf& baseConf); + const ShapeConf& baseConf, Length thickness); /// @brief Creates a square enclosure. /// @relatedalso World -inline Body* CreateSquareEnclosingBody(World& world, Length size, const ShapeConf& baseConf) +inline Body* CreateSquareEnclosingBody(World& world, Length size, const ShapeConf& baseConf, + Length thickness) { - return CreateRectangularEnclosingBody(world, Length2{size, size}, baseConf); + return CreateRectangularEnclosingBody(world, Length2{size, size}, baseConf, thickness); } /// @brief Finds body in given world that's closest to the given location. diff --git a/Testbed/Framework/Main.cpp b/Testbed/Framework/Main.cpp index 46aefec209..77b061d877 100644 --- a/Testbed/Framework/Main.cpp +++ b/Testbed/Framework/Main.cpp @@ -1147,18 +1147,17 @@ static void EntityUI(const Shape& shape) ImGui::ItemWidthContext itemWidthCtx(60); const auto density = GetDensity(shape); - const auto vertexRadius = GetVertexRadius(shape); + //const auto vertexRadius = GetVertexRadius(shape); const auto friction = GetFriction(shape); const auto restitution = GetRestitution(shape); const auto childCount = GetChildCount(shape); ImGui::LabelText("Density (kg/m^2)", "%.2e", static_cast(Real{density * SquareMeter / Kilogram})); - ImGui::LabelText("Vertex Radius (m)", "%.2e", - static_cast(Real{vertexRadius / Meter})); ImGui::LabelText("Friction", "%f", static_cast(friction)); ImGui::LabelText("Restitution", "%f", static_cast(restitution)); ImGui::LabelText("Child Count", "%u", childCount); + //ImGui::LabelText("Vertex Radius (m)", "%.2e", static_cast(Real{vertexRadius / Meter})); } static void EntityUI(Fixture& fixture) diff --git a/Testbed/Tests/Confined.hpp b/Testbed/Tests/Confined.hpp index 8b8b9b5afb..ee497786d7 100644 --- a/Testbed/Tests/Confined.hpp +++ b/Testbed/Tests/Confined.hpp @@ -95,7 +95,7 @@ class Confined : public Test Body* CreateEnclosure(Length vertexRadius, Length wallLength) { const auto body = CreateSquareEnclosingBody(m_world, wallLength, ShapeConf{ - }.UseVertexRadius(vertexRadius).UseRestitution(Finite(0))); + }.UseRestitution(Finite(0)), vertexRadius); SetLocation(*body, Length2{0_m, 20_m}); return body; } diff --git a/Testbed/Tests/DistanceTest.hpp b/Testbed/Tests/DistanceTest.hpp index 0e49f4f4d5..4fc64ec4cd 100644 --- a/Testbed/Tests/DistanceTest.hpp +++ b/Testbed/Tests/DistanceTest.hpp @@ -137,7 +137,7 @@ class DistanceTest : public Test if (body && fixture) { const auto shape = fixture->GetShape(); - const auto lastLegitVertexRadius = GetVertexRadius(shape); + const auto lastLegitVertexRadius = GetVertexRadius(shape, 0); const auto newVertexRadius = lastLegitVertexRadius - RadiusIncrement; if (newVertexRadius >= 0_m) { diff --git a/Testbed/Tests/FifteenPuzzle.hpp b/Testbed/Tests/FifteenPuzzle.hpp index 4dcf2f34ec..b1e2b33be7 100644 --- a/Testbed/Tests/FifteenPuzzle.hpp +++ b/Testbed/Tests/FifteenPuzzle.hpp @@ -43,9 +43,8 @@ namespace testbed { FifteenPuzzle(): Test(GetTestConf()) { m_gravity = LinearAcceleration2{}; - const auto enclosure = CreateSquareEnclosingBody(m_world, - 16_m + 2 * GetVertexRadius(), ShapeConf{} - .UseVertexRadius(GetVertexRadius())); + const auto enclosure = CreateSquareEnclosingBody(m_world, 16_m + 2 * GetVertexRadius(), + ShapeConf{}, GetVertexRadius()); SetLocation(*enclosure, GetCenter()); for (auto i = 0; i < 15; ++i) diff --git a/Testbed/Tests/JointsTest.hpp b/Testbed/Tests/JointsTest.hpp index a161a87052..8d28a684ff 100644 --- a/Testbed/Tests/JointsTest.hpp +++ b/Testbed/Tests/JointsTest.hpp @@ -132,7 +132,7 @@ class JointsTest: public Test Body* SetupContainer(Length2 center) { const auto b = CreateRectangularEnclosingBody(m_world, Length2{ColumnSize, RowSize}, - ShapeConf{}); + ShapeConf{}, DefaultLinearSlop * Real{2}); SetLocation(*b, center); return b; } @@ -211,8 +211,8 @@ class JointsTest: public Test { const auto containerBody = SetupContainer(center); - const auto sr = GetVertexRadius(m_smallDiskShape); - const auto nr = GetVertexRadius(m_diskShape); + const auto sr = GetVertexRadius(m_smallDiskShape, 0); + const auto nr = GetVertexRadius(m_diskShape, 0); const auto tr = sr + nr; const auto bd1 = BodyConf(DynamicBD).UseLocation(center - Length2{tr, 0_m}); const auto body1 = m_world.CreateBody(bd1); @@ -247,11 +247,11 @@ class JointsTest: public Test const auto joint3 = static_cast(m_world.CreateJoint(jd3)); auto jd4 = GearJointConf{joint1, joint2}; - jd4.ratio = GetVertexRadius(m_diskShape) / GetVertexRadius(m_smallDiskShape); + jd4.ratio = GetVertexRadius(m_diskShape, 0) / GetVertexRadius(m_smallDiskShape, 0); m_gearJoint0 = static_cast(m_world.CreateJoint(jd4)); auto jd5 = GearJointConf{joint2, joint3}; - jd5.ratio = -1.0f / (GetVertexRadius(m_diskShape) / 1_m); + jd5.ratio = -1.0f / (GetVertexRadius(m_diskShape, 0) / 1_m); m_gearJoint1 = static_cast(m_world.CreateJoint(jd5)); } diff --git a/Testbed/Tests/OneSidedPlatform.hpp b/Testbed/Tests/OneSidedPlatform.hpp index 5355f1d6ae..aaefda9cff 100644 --- a/Testbed/Tests/OneSidedPlatform.hpp +++ b/Testbed/Tests/OneSidedPlatform.hpp @@ -84,7 +84,7 @@ class OneSidedPlatform : public Test #if 1 const auto position = m_character->GetBody()->GetLocation(); - if (GetY(position) < m_top + m_radius - GetVertexRadius(m_platform->GetShape())) + if (GetY(position) < m_top + m_radius - GetVertexRadius(m_platform->GetShape(), 0)) { contact.UnsetEnabled(); } diff --git a/UnitTests/Body.cpp b/UnitTests/Body.cpp index c9e3f33813..bac1755ebc 100644 --- a/UnitTests/Body.cpp +++ b/UnitTests/Body.cpp @@ -362,7 +362,7 @@ TEST(Body, CreateAndDestroyFixture) { auto fixture = body->CreateFixture(shape, FixtureConf{}, false); const auto fshape = fixture->GetShape(); - EXPECT_EQ(GetVertexRadius(fshape), GetVertexRadius(shape)); + EXPECT_EQ(GetVertexRadius(fshape, 0), GetVertexRadius(shape, 0)); EXPECT_EQ(static_cast(GetData(fshape))->GetLocation(), conf.GetLocation()); EXPECT_FALSE(body->GetFixtures().empty()); { @@ -392,7 +392,7 @@ TEST(Body, CreateAndDestroyFixture) { auto fixture = body->CreateFixture(shape, FixtureConf{}, false); const auto fshape = fixture->GetShape(); - EXPECT_EQ(GetVertexRadius(fshape), GetVertexRadius(shape)); + EXPECT_EQ(GetVertexRadius(fshape, 0), GetVertexRadius(shape, 0)); EXPECT_EQ(static_cast(GetData(fshape))->GetLocation(), conf.GetLocation()); EXPECT_FALSE(body->GetFixtures().empty()); { diff --git a/UnitTests/ChainShape.cpp b/UnitTests/ChainShape.cpp index 6cbba8826b..ce8170d844 100644 --- a/UnitTests/ChainShape.cpp +++ b/UnitTests/ChainShape.cpp @@ -64,8 +64,12 @@ TEST(ChainShapeConf, DefaultConstruction) EXPECT_EQ(GetChildCount(foo), ChildCounter{0}); EXPECT_EQ(foo.GetVertexCount(), ChildCounter{0}); EXPECT_EQ(GetMassData(foo), defaultMassData); - - EXPECT_EQ(GetVertexRadius(foo), ChainShapeConf::GetDefaultVertexRadius()); + for (auto i = ChildCounter{0}; i < GetChildCount(foo); ++i) + { + EXPECT_EQ(GetVertexRadius(foo, i), ChainShapeConf::GetDefaultVertexRadius()); + } + EXPECT_THROW(GetChild(foo, GetChildCount(foo)), InvalidArgument); + EXPECT_EQ(GetVertexRadius(foo, GetChildCount(foo)), ChainShapeConf::GetDefaultVertexRadius()); EXPECT_EQ(GetDensity(foo), defaultConf.density); EXPECT_EQ(GetFriction(foo), defaultConf.friction); EXPECT_EQ(GetRestitution(foo), defaultConf.restitution); @@ -133,7 +137,10 @@ TEST(ChainShapeConf, OneVertexLikeDisk) auto foo = ChainShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); EXPECT_EQ(foo.GetVertexCount(), ChildCounter{1}); - EXPECT_EQ(GetVertexRadius(foo), vertexRadius); + for (auto i = ChildCounter{0}; i < GetChildCount(foo); ++i) + { + EXPECT_EQ(GetVertexRadius(foo, i), vertexRadius); + } EXPECT_EQ(GetMassData(foo), expectedMassData); const auto child = GetChild(foo, 0); @@ -156,7 +163,10 @@ TEST(ChainShapeConf, TwoVertexLikeEdge) auto foo = ChainShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); EXPECT_EQ(foo.GetVertexCount(), ChildCounter{2}); - EXPECT_EQ(GetVertexRadius(foo), vertexRadius); + for (auto i = ChildCounter{0}; i < GetChildCount(foo); ++i) + { + EXPECT_EQ(GetVertexRadius(foo, i), vertexRadius); + } } TEST(ChainShapeConf, TwoVertexDpLikeEdgeDp) @@ -228,8 +238,10 @@ TEST(ChainShapeConf, FourVertex) auto foo = ChainShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{4}); EXPECT_EQ(foo.GetVertexCount(), ChildCounter{5}); - EXPECT_EQ(GetVertexRadius(foo), vertexRadius); - + for (auto i = ChildCounter{0}; i < GetChildCount(foo); ++i) + { + EXPECT_EQ(GetVertexRadius(foo, i), vertexRadius); + } const auto massData = GetMassData(foo); EXPECT_EQ(massData.center, (Length2{})); const auto expectedMass = Mass{edgeMassData0.mass} * Real(4); @@ -250,8 +262,10 @@ TEST(ChainShapeConf, WithCircleVertices) auto foo = ChainShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{4}); EXPECT_EQ(foo.GetVertexCount(), ChildCounter{5}); - EXPECT_EQ(GetVertexRadius(foo), vertexRadius); - + for (auto i = ChildCounter{0}; i < GetChildCount(foo); ++i) + { + EXPECT_EQ(GetVertexRadius(foo, i), vertexRadius); + } const auto massData = GetMassData(foo); EXPECT_NEAR(static_cast(Real(GetX(massData.center) / 1_m)), 0.0, 0.0001); EXPECT_NEAR(static_cast(Real(GetY(massData.center) / 1_m)), 2.4142134189605713, 0.0001); diff --git a/UnitTests/DiskShape.cpp b/UnitTests/DiskShape.cpp index 3016d95200..b008f8b3b3 100644 --- a/UnitTests/DiskShape.cpp +++ b/UnitTests/DiskShape.cpp @@ -66,7 +66,7 @@ TEST(DiskShapeConf, InitConstruction) EXPECT_EQ(typeid(foo), typeid(Shape)); EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); - EXPECT_EQ(GetVertexRadius(foo), radius); + EXPECT_EQ(GetVertexRadius(foo, 0), radius); EXPECT_EQ(GetX(conf.GetLocation()), GetX(position)); EXPECT_EQ(GetY(conf.GetLocation()), GetY(position)); } @@ -173,8 +173,8 @@ TEST(DiskShapeConf, Equality) EXPECT_FALSE(DiskShapeConf().UseLocation(Length2(1_m, 2_m)) == DiskShapeConf()); EXPECT_TRUE(DiskShapeConf().UseLocation(Length2(1_m, 2_m)) == DiskShapeConf().UseLocation(Length2(1_m, 2_m))); - EXPECT_FALSE(DiskShapeConf().UseVertexRadius(10_m) == DiskShapeConf()); - EXPECT_TRUE(DiskShapeConf().UseVertexRadius(10_m) == DiskShapeConf().UseVertexRadius(10_m)); + EXPECT_FALSE(DiskShapeConf().UseRadius(10_m) == DiskShapeConf()); + EXPECT_TRUE(DiskShapeConf().UseRadius(10_m) == DiskShapeConf().UseRadius(10_m)); EXPECT_FALSE(DiskShapeConf().UseDensity(10_kgpm2) == DiskShapeConf()); EXPECT_TRUE(DiskShapeConf().UseDensity(10_kgpm2) == DiskShapeConf().UseDensity(10_kgpm2)); @@ -194,8 +194,8 @@ TEST(DiskShapeConf, Inequality) EXPECT_TRUE(DiskShapeConf().UseLocation(Length2(1_m, 2_m)) != DiskShapeConf()); EXPECT_FALSE(DiskShapeConf().UseLocation(Length2(1_m, 2_m)) != DiskShapeConf().UseLocation(Length2(1_m, 2_m))); - EXPECT_TRUE(DiskShapeConf().UseVertexRadius(10_m) != DiskShapeConf()); - EXPECT_FALSE(DiskShapeConf().UseVertexRadius(10_m) != DiskShapeConf().UseVertexRadius(10_m)); + EXPECT_TRUE(DiskShapeConf().UseRadius(10_m) != DiskShapeConf()); + EXPECT_FALSE(DiskShapeConf().UseRadius(10_m) != DiskShapeConf().UseRadius(10_m)); EXPECT_TRUE(DiskShapeConf().UseDensity(10_kgpm2) != DiskShapeConf()); EXPECT_FALSE(DiskShapeConf().UseDensity(10_kgpm2) != DiskShapeConf().UseDensity(10_kgpm2)); diff --git a/UnitTests/MultiShape.cpp b/UnitTests/MultiShape.cpp index 33f53614ed..109f205df5 100644 --- a/UnitTests/MultiShape.cpp +++ b/UnitTests/MultiShape.cpp @@ -43,14 +43,14 @@ TEST(MultiShapeConf, ByteSize) #if !defined(NDEBUG) EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(36)); #else - EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(28)); + EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(24)); #endif #else EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(40)); #endif break; - case 8: EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(56)); break; - case 16: EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(96)); break; + case 8: EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(48)); break; + case 16: EXPECT_EQ(sizeof(MultiShapeConf), std::size_t(80)); break; default: FAIL(); break; } } @@ -64,11 +64,11 @@ TEST(MultiShapeConf, DefaultConstruction) EXPECT_EQ(typeid(foo), typeid(MultiShapeConf)); EXPECT_EQ(GetChildCount(foo), ChildCounter{0}); EXPECT_EQ(GetMassData(foo), defaultMassData); - - EXPECT_EQ(GetVertexRadius(foo), MultiShapeConf::GetDefaultVertexRadius()); EXPECT_EQ(GetDensity(foo), defaultConf.density); EXPECT_EQ(GetFriction(foo), defaultConf.friction); EXPECT_EQ(GetRestitution(foo), defaultConf.restitution); + EXPECT_THROW(GetChild(foo, 0), InvalidArgument); + EXPECT_THROW(GetVertexRadius(foo, 0), InvalidArgument); } TEST(MultiShapeConf, GetInvalidChildThrows) @@ -128,6 +128,7 @@ TEST(MultiShapeConf, BaseVisitorForDiskShape) EXPECT_TRUE(visitor.IsVisited()); } #endif + TEST(MultiShapeConf, AddConvexHullWithOnePointSameAsDisk) { const auto defaultMassData = MassData{}; @@ -140,17 +141,16 @@ TEST(MultiShapeConf, AddConvexHullWithOnePointSameAsDisk) auto conf = MultiShapeConf{}; conf.density = 2.3_kgpm2; - conf.vertexRadius = 0.7_m; auto foo = MultiShapeConf{conf}; ASSERT_EQ(GetChildCount(foo), ChildCounter{0}); ASSERT_EQ(GetMassData(foo), defaultMassData); - ASSERT_EQ(GetVertexRadius(foo), conf.vertexRadius); ASSERT_EQ(GetDensity(foo), conf.density); - conf.AddConvexHull(pointSet); + conf.AddConvexHull(pointSet, 0.7_m); foo = MultiShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); + EXPECT_EQ(GetVertexRadius(foo, 0), 0.7_m); const auto child = GetChild(foo, 0); EXPECT_EQ(child.GetVertexCount(), VertexCounter(1)); @@ -159,7 +159,7 @@ TEST(MultiShapeConf, AddConvexHullWithOnePointSameAsDisk) EXPECT_NE(massData, defaultMassData); EXPECT_EQ(massData.center, center); - const auto diskMassData = playrho::d2::GetMassData(conf.vertexRadius, conf.density, center); + const auto diskMassData = playrho::d2::GetMassData(0.7_m, conf.density, center); EXPECT_EQ(massData, diskMassData); } @@ -177,18 +177,17 @@ TEST(MultiShapeConf, AddConvexHullWithTwoPointsSameAsEdge) auto conf = MultiShapeConf{}; conf.density = 2.3_kgpm2; - conf.vertexRadius = 0.7_m; auto foo = MultiShapeConf{conf}; ASSERT_EQ(GetChildCount(foo), ChildCounter{0}); ASSERT_EQ(GetMassData(foo), defaultMassData); - ASSERT_EQ(GetVertexRadius(foo), conf.vertexRadius); ASSERT_EQ(GetDensity(foo), conf.density); - conf.AddConvexHull(pointSet); + conf.AddConvexHull(pointSet, 0.7_m); foo = MultiShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); - + EXPECT_EQ(GetVertexRadius(foo, 0), 0.7_m); + const auto child = GetChild(foo, 0); EXPECT_EQ(child.GetVertexCount(), VertexCounter(2)); @@ -196,7 +195,7 @@ TEST(MultiShapeConf, AddConvexHullWithTwoPointsSameAsEdge) EXPECT_NE(massData, defaultMassData); EXPECT_EQ(massData.center, (p0 + p1) / Real(2)); - const auto edgeMassData = playrho::d2::GetMassData(conf.vertexRadius, conf.density, p0, p1); + const auto edgeMassData = playrho::d2::GetMassData(0.7_m, conf.density, p0, p1); EXPECT_EQ(massData.center, edgeMassData.center); /// @note Units of L^-2 M^-1 QP^2. @@ -217,12 +216,10 @@ TEST(MultiShapeConf, AddTwoConvexHullWithOnePoint) auto conf = MultiShapeConf{}; conf.density = 2.3_kgpm2; - conf.vertexRadius = 0.7_m; auto foo = MultiShapeConf{conf}; ASSERT_EQ(GetChildCount(foo), ChildCounter{0}); ASSERT_EQ(GetMassData(foo), defaultMassData); - ASSERT_EQ(GetVertexRadius(foo), conf.vertexRadius); ASSERT_EQ(GetDensity(foo), conf.density); pointSet.clear(); @@ -230,9 +227,10 @@ TEST(MultiShapeConf, AddTwoConvexHullWithOnePoint) pointSet.add(p0); ASSERT_EQ(pointSet.size(), std::size_t(1)); - conf.AddConvexHull(pointSet); + conf.AddConvexHull(pointSet, 0.7_m); foo = MultiShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{1}); + EXPECT_EQ(GetVertexRadius(foo, 0), 0.7_m); const auto child0 = GetChild(foo, 0); EXPECT_EQ(child0.GetVertexCount(), VertexCounter(1)); @@ -243,10 +241,11 @@ TEST(MultiShapeConf, AddTwoConvexHullWithOnePoint) pointSet.add(p1); ASSERT_EQ(pointSet.size(), std::size_t(1)); - conf.AddConvexHull(pointSet); + conf.AddConvexHull(pointSet, 0.7_m); foo = MultiShapeConf{conf}; EXPECT_EQ(GetChildCount(foo), ChildCounter{2}); - + EXPECT_EQ(GetVertexRadius(foo, 1), 0.7_m); + const auto child1 = GetChild(foo, 1); EXPECT_EQ(child1.GetVertexCount(), VertexCounter(1)); EXPECT_EQ(child1.GetVertex(0), p1); @@ -255,8 +254,8 @@ TEST(MultiShapeConf, AddTwoConvexHullWithOnePoint) EXPECT_NE(massData, defaultMassData); EXPECT_EQ(massData.center, (p0 + p1) / Real(2)); - const auto massDataP0 = playrho::d2::GetMassData(conf.vertexRadius, conf.density, p0); - const auto massDataP1 = playrho::d2::GetMassData(conf.vertexRadius, conf.density, p1); + const auto massDataP0 = playrho::d2::GetMassData(0.7_m, conf.density, p0); + const auto massDataP1 = playrho::d2::GetMassData(0.7_m, conf.density, p1); EXPECT_EQ(massData.mass, Mass{massDataP0.mass} + Mass{massDataP1.mass}); EXPECT_EQ(massData.I, RotInertia{massDataP0.I} + RotInertia{massDataP1.I}); } @@ -271,9 +270,6 @@ TEST(MultiShapeConf, Equality) EXPECT_FALSE(MultiShapeConf().AddConvexHull(pointSet) == MultiShapeConf()); EXPECT_TRUE(MultiShapeConf().AddConvexHull(pointSet) == MultiShapeConf().AddConvexHull(pointSet)); - EXPECT_FALSE(MultiShapeConf().UseVertexRadius(10_m) == MultiShapeConf()); - EXPECT_TRUE(MultiShapeConf().UseVertexRadius(10_m) == MultiShapeConf().UseVertexRadius(10_m)); - EXPECT_FALSE(MultiShapeConf().UseDensity(10_kgpm2) == MultiShapeConf()); EXPECT_TRUE(MultiShapeConf().UseDensity(10_kgpm2) == MultiShapeConf().UseDensity(10_kgpm2)); @@ -294,9 +290,6 @@ TEST(MultiShapeConf, Inequality) EXPECT_TRUE(MultiShapeConf().AddConvexHull(pointSet) != MultiShapeConf()); EXPECT_FALSE(MultiShapeConf().AddConvexHull(pointSet) != MultiShapeConf().AddConvexHull(pointSet)); - EXPECT_TRUE(MultiShapeConf().UseVertexRadius(10_m) != MultiShapeConf()); - EXPECT_FALSE(MultiShapeConf().UseVertexRadius(10_m) != MultiShapeConf().UseVertexRadius(10_m)); - EXPECT_TRUE(MultiShapeConf().UseDensity(10_kgpm2) != MultiShapeConf()); EXPECT_FALSE(MultiShapeConf().UseDensity(10_kgpm2) != MultiShapeConf().UseDensity(10_kgpm2)); diff --git a/UnitTests/Shape.cpp b/UnitTests/Shape.cpp index 2bc8dec20a..2cc92aadeb 100644 --- a/UnitTests/Shape.cpp +++ b/UnitTests/Shape.cpp @@ -50,7 +50,7 @@ TEST(Shape, ByteSize) TEST(Shape, Traits) { - EXPECT_FALSE(std::is_default_constructible::value); + EXPECT_TRUE(std::is_default_constructible::value); EXPECT_FALSE(std::is_nothrow_default_constructible::value); EXPECT_FALSE(std::is_trivially_default_constructible::value); @@ -86,6 +86,21 @@ TEST(Shape, Traits) EXPECT_FALSE(std::is_trivially_destructible::value); } +TEST(Shape, DefaultConstruction) +{ + const auto s = Shape{}; + EXPECT_EQ(GetMassData(s), MassData()); + EXPECT_EQ(GetFriction(s), Real(0)); + EXPECT_EQ(GetRestitution(s), Real(0)); + EXPECT_EQ(GetDensity(s), 0_kgpm2); + EXPECT_THROW(GetVertexRadius(s, 0), InvalidArgument); + EXPECT_EQ(GetChildCount(s), 0); + EXPECT_THROW(GetChild(s, 0), InvalidArgument); + EXPECT_TRUE(s == s); + const auto t = Shape{}; + EXPECT_TRUE(s == t); +} + TEST(Shape, types) { const auto sc = DiskShapeConf{1_m}; @@ -234,7 +249,7 @@ MassData GetMassData(const X&) noexcept return MassData{}; } -NonNegative GetVertexRadius(const X&) noexcept +NonNegative GetVertexRadius(const X&, ChildCounter) noexcept { return 0_m; } diff --git a/UnitTests/World.cpp b/UnitTests/World.cpp index 08732392bd..a6de2cde1d 100644 --- a/UnitTests/World.cpp +++ b/UnitTests/World.cpp @@ -931,7 +931,7 @@ TEST(World, CreateSquareEnclosingBody) { World world; Body* body = nullptr; - EXPECT_NO_THROW(body = CreateSquareEnclosingBody(world, 2_m, ShapeConf{})); + EXPECT_NO_THROW(body = CreateSquareEnclosingBody(world, 2_m, ShapeConf{}, DefaultLinearSlop * Real{2})); ASSERT_NE(body, nullptr); EXPECT_EQ(body->GetType(), BodyType::Static); const auto fixtures = body->GetFixtures(); @@ -965,21 +965,18 @@ TEST(World, GetTouchingCountFreeFunction) world.Step(stepConf); EXPECT_EQ(GetTouchingCount(world), ContactCounter(0)); - const auto groundConf = EdgeShapeConf{} .Set(Vec2(-40.0f, 0.0f) * Meter, Vec2(40.0f, 0.0f) * Meter); - const auto ground = world.CreateBody(); ground->CreateFixture(Shape(groundConf)); - const auto bd = BodyConf{}.UseType(BodyType::Dynamic); - const auto lowerBodyConf = BodyConf(bd).UseLocation(Vec2(0.0f, 0.5f) * Meter); + const auto lowerBodyConf = BodyConf{}.UseType(BodyType::Dynamic).UseLocation(Vec2(0.0f, 0.5f) * Meter); const auto diskConf = DiskShapeConf{}.UseDensity(10_kgpm2); const auto smallerDiskConf = DiskShapeConf(diskConf).UseRadius(0.5_m); - const auto lowerBody = world.CreateBody(lowerBodyConf); lowerBody->CreateFixture(Shape(smallerDiskConf)); + ASSERT_EQ(GetAwakeCount(world), 1); while (GetAwakeCount(world) > 0) { world.Step(stepConf); @@ -1019,14 +1016,14 @@ TEST(World, DynamicEdgeBodyHasCorrectMass) const auto v2 = Length2{+1_m, 0_m}; const auto conf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2); const auto shape = Shape{conf}; - ASSERT_EQ(GetVertexRadius(shape), 1_m); + ASSERT_EQ(GetVertexRadius(shape, 0), 1_m); const auto fixture = body->CreateFixture(shape); ASSERT_NE(fixture, nullptr); ASSERT_EQ(fixture->GetDensity(), 1_kgpm2); - const auto circleMass = Mass{fixture->GetDensity() * (Pi * Square(GetVertexRadius(shape)))}; - const auto rectMass = Mass{fixture->GetDensity() * (GetVertexRadius(shape) * 2 * GetMagnitude(v2 - v1))}; + const auto circleMass = Mass{fixture->GetDensity() * (Pi * Square(GetVertexRadius(shape, 0)))}; + const auto rectMass = Mass{fixture->GetDensity() * (GetVertexRadius(shape, 0) * 2 * GetMagnitude(v2 - v1))}; const auto totalMass = Mass{circleMass + rectMass}; EXPECT_EQ(body->GetType(), BodyType::Dynamic); @@ -1609,9 +1606,11 @@ TEST(World, HeavyOnLight) auto upperBodysLowestPoint = GetY(upperBody->GetLocation()); auto numSteps = 0ul; + EXPECT_EQ(GetAwakeCount(world), 2); while (GetAwakeCount(world) > 0) { world.Step(smallerStepConf); + EXPECT_EQ(GetTouchingCount(world), 2); upperBodysLowestPoint = std::min(upperBodysLowestPoint, GetY(upperBody->GetLocation())); ++numSteps; } @@ -1629,6 +1628,25 @@ TEST(World, HeavyOnLight) EXPECT_NEAR(static_cast(Real(upperBodysLowestPoint / Meter)), 5.9476470947265625, 0.001); } + + // Create upper body, then lower body using the smaller step conf, and using sensors + { + auto world = World{WorldConf{}.UseMinVertexRadius(SmallerLinearSlop)}; + const auto ground = world.CreateBody(); + ground->CreateFixture(Shape(groundConf)); + + const auto upperBody = world.CreateBody(upperBodyConf); + const auto lowerBody = world.CreateBody(lowerBodyConf); + ASSERT_LT(GetY(lowerBody->GetLocation()), GetY(upperBody->GetLocation())); + + lowerBody->CreateFixture(Shape(smallerDiskConf), FixtureConf{}.UseIsSensor(true)); + upperBody->CreateFixture(Shape(biggerDiskConf), FixtureConf{}.UseIsSensor(true)); + ASSERT_LT(GetMass(*lowerBody), GetMass(*upperBody)); + + EXPECT_EQ(GetAwakeCount(world), 2); + world.Step(smallerStepConf); + EXPECT_EQ(GetTouchingCount(world), 2); + } } TEST(World, PerfectlyOverlappedSameCirclesStayPut)