PlayRho  1.1.0
An Interactive Real-Time-Oriented C++ Physics Engine & Library
Shape.cpp

This is the googletest based unit testing file for the interfaces to playrho::d2::Shape.

/*
* Copyright (c) 2020 Louis Langholtz https://github.com/louis-langholtz/PlayRho
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "UnitTests.hpp"
#include <PlayRho/Collision/Shapes/Shape.hpp>
#include <PlayRho/Collision/Shapes/EdgeShapeConf.hpp>
#include <PlayRho/Collision/Shapes/DiskShapeConf.hpp>
#include <PlayRho/Collision/Shapes/PolygonShapeConf.hpp>
#include <PlayRho/Collision/Distance.hpp>
#include <PlayRho/Collision/Manifold.hpp>
#include <any>
#include <chrono>
using namespace playrho;
using namespace playrho::d2;
TEST(Shape, ByteSize)
{
// Check size at test runtime instead of compile-time via static_assert to avoid stopping
// builds and to report actual size rather than just reporting that expected size is wrong.
#if defined(_WIN32) && !defined(_WIN64)
EXPECT_EQ(sizeof(Shape), std::size_t(8));
#else
EXPECT_EQ(sizeof(Shape), std::size_t(16));
#endif
EXPECT_EQ(sizeof(Shape), sizeof(std::shared_ptr<int>));
}
TEST(Shape, Traits)
{
// NOTE: Double parenthesis needed sometimes for proper macro expansion.
EXPECT_TRUE(std::is_default_constructible<Shape>::value);
EXPECT_TRUE(std::is_nothrow_default_constructible<Shape>::value);
EXPECT_FALSE(std::is_trivially_default_constructible<Shape>::value);
// Construction with any 1 supporting argument should succeed...
using X = DiskShapeConf;
EXPECT_TRUE((std::is_constructible<Shape, X>::value));
EXPECT_FALSE((std::is_nothrow_constructible<Shape, X>::value));
EXPECT_FALSE((std::is_trivially_constructible<Shape, X>::value));
// Construction with 2 arguments should fail...
EXPECT_FALSE((std::is_constructible<Shape, X, X>::value));
EXPECT_FALSE((std::is_nothrow_constructible<Shape, X, X>::value));
EXPECT_FALSE((std::is_trivially_constructible<Shape, X, X>::value));
EXPECT_TRUE(std::is_copy_constructible<Shape>::value);
EXPECT_TRUE(std::is_nothrow_copy_constructible<Shape>::value);
EXPECT_FALSE(std::is_trivially_copy_constructible<Shape>::value);
EXPECT_TRUE(std::is_move_constructible<Shape>::value);
EXPECT_TRUE(std::is_nothrow_move_constructible<Shape>::value);
EXPECT_FALSE(std::is_trivially_move_constructible<Shape>::value);
EXPECT_TRUE(std::is_copy_assignable<Shape>::value);
EXPECT_TRUE(std::is_nothrow_copy_assignable<Shape>::value);
EXPECT_FALSE(std::is_trivially_copy_assignable<Shape>::value);
EXPECT_TRUE(std::is_move_assignable<Shape>::value);
EXPECT_TRUE(std::is_nothrow_move_assignable<Shape>::value);
EXPECT_FALSE(std::is_trivially_move_assignable<Shape>::value);
EXPECT_TRUE(std::is_destructible<Shape>::value);
EXPECT_TRUE(std::is_nothrow_destructible<Shape>::value);
EXPECT_FALSE(std::is_trivially_destructible<Shape>::value);
// The value initializing constructor resolves for ineligible types but preferably any such
// instantiation will not actually compile.
EXPECT_TRUE((std::is_constructible<Shape, int>::value));
}
TEST(Shape, DefaultConstruction)
{
const auto s = Shape{};
EXPECT_FALSE(s.has_value());
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), ChildCounter(0));
EXPECT_THROW(GetChild(s, 0), InvalidArgument);
EXPECT_TRUE(s == s);
auto t = Shape{};
EXPECT_TRUE(s == t);
EXPECT_NO_THROW(Transform(t, Mat22{}));
EXPECT_EQ(GetType(s), GetTypeID<void>());
}
namespace sans_some {
namespace {
struct ShapeTest {
int number;
};
[[maybe_unused]] void Transform(ShapeTest&, const Mat22&)
{
}
} // namespace
} // namespace sans_none
TEST(Shape, InitializingConstructor)
{
EXPECT_TRUE((std::is_constructible<Shape, ::sans_some::ShapeTest>::value));
EXPECT_TRUE((std::is_constructible<Shape, DiskShapeConf>::value));
auto conf = DiskShapeConf{};
auto s = Shape{conf};
EXPECT_TRUE(s.has_value());
EXPECT_EQ(GetChildCount(s), ChildCounter(1));
}
TEST(Shape, Assignment)
{
auto s = Shape{};
ASSERT_EQ(GetType(s), GetTypeID<void>());
ASSERT_EQ(GetChildCount(s), ChildCounter(0));
ASSERT_EQ(GetFriction(s), Real(0));
ASSERT_EQ(GetRestitution(s), Real(0));
ASSERT_EQ(GetDensity(s), 0_kgpm2);
const auto friction = Real(0.1);
const auto restitution = Real(0.2);
const auto density = 0.4_kgpm2;
s = DiskShapeConf{1_m}.UseFriction(friction).UseRestitution(restitution).UseDensity(density);
EXPECT_NE(GetType(s), GetTypeID<void>());
EXPECT_EQ(GetType(s), GetTypeID<DiskShapeConf>());
EXPECT_EQ(GetChildCount(s), ChildCounter(1));
EXPECT_EQ(GetFriction(s), friction);
EXPECT_EQ(GetRestitution(s), restitution);
EXPECT_EQ(GetDensity(s), density);
EXPECT_NE(GetType(s), GetTypeID<void>());
EXPECT_EQ(GetType(s), GetTypeID<EdgeShapeConf>());
}
{
const auto shape = Shape{};
EXPECT_THROW(TypeCast<int>(shape), std::bad_cast);
}
TEST(Shape, ForConstantDataTypeCastIsLikeAnyCast)
{
const auto foo = Shape{DiskShapeConf{1_m}};
const auto bar = std::any{DiskShapeConf{1_m}};
EXPECT_TRUE(TypeCast<const DiskShapeConf*>(&foo) == nullptr);
EXPECT_TRUE(std::any_cast<const DiskShapeConf*>(&bar) == nullptr);
EXPECT_TRUE(TypeCast<DiskShapeConf*>(&foo) == nullptr);
EXPECT_TRUE(std::any_cast<DiskShapeConf*>(&bar) == nullptr);
EXPECT_TRUE(TypeCast<const DiskShapeConf>(&foo) != nullptr);
EXPECT_TRUE(std::any_cast<const DiskShapeConf>(&bar) != nullptr);
EXPECT_TRUE(TypeCast<DiskShapeConf>(&foo) != nullptr);
EXPECT_TRUE(std::any_cast<DiskShapeConf>(&bar) != nullptr);
}
TEST(Shape, ForMutableDataTypeCastIsLikeAnyCast)
{
auto foo = Shape{DiskShapeConf{1_m}};
auto bar = std::any{DiskShapeConf{1_m}};
EXPECT_TRUE(TypeCast<const DiskShapeConf*>(&foo) == nullptr);
EXPECT_TRUE(std::any_cast<const DiskShapeConf*>(&bar) == nullptr);
EXPECT_TRUE(TypeCast<DiskShapeConf*>(&foo) == nullptr);
EXPECT_TRUE(std::any_cast<DiskShapeConf*>(&bar) == nullptr);
EXPECT_TRUE(TypeCast<const DiskShapeConf>(&foo) != nullptr);
EXPECT_TRUE(std::any_cast<const DiskShapeConf>(&bar) != nullptr);
EXPECT_TRUE(TypeCast<DiskShapeConf>(&foo) != nullptr);
EXPECT_TRUE(std::any_cast<DiskShapeConf>(&bar) != nullptr);
}
TEST(Shape, types)
{
EXPECT_EQ(GetTypeID<DiskShapeConf>(), GetTypeID<DiskShapeConf>());
const auto sc = DiskShapeConf{1_m};
EXPECT_EQ(GetTypeID(sc), GetTypeID<DiskShapeConf>());
EXPECT_EQ(GetTypeID<DiskShapeConf>(), GetTypeID(sc));
EXPECT_EQ(GetTypeID(sc), GetTypeID(sc));
EXPECT_NE(GetTypeID<DiskShapeConf>(), GetTypeID<EdgeShapeConf>());
const auto s1 = Shape{sc};
ASSERT_EQ(GetTypeID<Shape>(), GetTypeID(s1));
EXPECT_EQ(GetType(s1), GetTypeID<DiskShapeConf>());
const auto& st1 = GetType(s1);
ASSERT_NE(st1, GetTypeID<Shape>());
EXPECT_EQ(st1, GetTypeID(sc));
const auto s2 = Shape{s1}; // This should copy construct
const auto& st2 = GetType(s2);
EXPECT_EQ(st2, GetTypeID(sc)); // Confirm s2 was a copy construction
}
TEST(Shape, TestOverlapSlowerThanCollideShapesForCircles)
{
const auto shape = DiskShapeConf{2_m};
const auto xfm = Transformation{Length2{}, UnitVec::GetRight()};
const auto child = GetChild(shape, 0);
const auto maxloops = 1000000u;
std::chrono::duration<double> elapsed_test_overlap;
std::chrono::duration<double> elapsed_collide_shapes;
for (auto attempt = 0u; attempt < 2u; ++attempt)
{
{
auto count = 0u;
const auto start = std::chrono::high_resolution_clock::now();
for (auto i = decltype(maxloops){0}; i < maxloops; ++i)
{
if (TestOverlap(child, xfm, child, xfm) >= 0_m2)
{
++count;
}
}
const auto end = std::chrono::high_resolution_clock::now();
elapsed_test_overlap = end - start;
ASSERT_EQ(count, maxloops);
}
{
auto count = 0u;
const auto start = std::chrono::high_resolution_clock::now();
for (auto i = decltype(maxloops){0}; i < maxloops; ++i)
{
const auto manifold = CollideShapes(child, xfm, child, xfm);
if (manifold.GetPointCount() > 0)
{
++count;
}
}
const auto end = std::chrono::high_resolution_clock::now();
elapsed_collide_shapes = end - start;
ASSERT_EQ(count, maxloops);
}
EXPECT_GT(elapsed_test_overlap.count(), elapsed_collide_shapes.count());
}
}
TEST(Shape, TestOverlapFasterThanCollideShapesForPolygons)
{
const auto shape = PolygonShapeConf{2_m, 2_m};
const auto xfm = Transformation{Length2{}, UnitVec::GetRight()};
const auto child = GetChild(shape, 0);
const auto maxloops = 1000000u;
std::chrono::duration<double> elapsed_test_overlap;
std::chrono::duration<double> elapsed_collide_shapes;
for (auto attempt = 0u; attempt < 2u; ++attempt)
{
{
auto count = 0u;
const auto start = std::chrono::high_resolution_clock::now();
for (auto i = decltype(maxloops){0}; i < maxloops; ++i)
{
if (TestOverlap(child, xfm, child, xfm) >= 0_m2)
{
++count;
}
}
const auto end = std::chrono::high_resolution_clock::now();
elapsed_test_overlap = end - start;
ASSERT_EQ(count, maxloops);
}
{
auto count = 0u;
const auto start = std::chrono::high_resolution_clock::now();
for (auto i = decltype(maxloops){0}; i < maxloops; ++i)
{
const auto manifold = CollideShapes(child, xfm, child, xfm);
if (manifold.GetPointCount() > 0)
{
++count;
}
}
const auto end = std::chrono::high_resolution_clock::now();
elapsed_collide_shapes = end - start;
ASSERT_EQ(count, maxloops);
}
EXPECT_LT(elapsed_test_overlap.count(), elapsed_collide_shapes.count());
}
}
TEST(Shape, Equality)
{
EXPECT_TRUE(Shape(EdgeShapeConf()) == Shape(EdgeShapeConf()));
const auto shapeA = Shape(DiskShapeConf{}.UseRadius(100_m));
const auto shapeB = Shape(DiskShapeConf{}.UseRadius(100_m));
EXPECT_TRUE(shapeA == shapeB);
EXPECT_FALSE(Shape(DiskShapeConf()) == Shape(EdgeShapeConf()));
}
TEST(Shape, Inequality)
{
EXPECT_FALSE(Shape(EdgeShapeConf()) != Shape(EdgeShapeConf()));
const auto shapeA = Shape(DiskShapeConf{}.UseRadius(100_m));
const auto shapeB = Shape(DiskShapeConf{}.UseRadius(100_m));
EXPECT_FALSE(shapeA != shapeB);
EXPECT_TRUE(Shape(DiskShapeConf()) != Shape(EdgeShapeConf()));
}
{
const auto oldConf = DiskShapeConf{}.UseRadius(1_m).UseLocation(Length2{2_m, 0_m});
auto newConf = DiskShapeConf{};
auto shape = Shape(oldConf);
EXPECT_NO_THROW(Transform(shape, GetIdentity<Mat22>()));
newConf = TypeCast<DiskShapeConf>(shape);
EXPECT_EQ(oldConf.GetLocation(), newConf.GetLocation());
EXPECT_NO_THROW(Transform(shape, Mat22{Vec2{Real(2), Real(0)}, Vec2{Real(0), Real(2)}}));
newConf = TypeCast<DiskShapeConf>(shape);
EXPECT_EQ(Length2(4_m, 0_m), newConf.GetLocation());
}
playrho::d2::GetMassData
MassData GetMassData(Length r, NonNegative< AreaDensity > density, Length2 location)
Computes the mass data for a circular shape.
Definition: MassData.cpp:31
playrho::d2
Name space for 2-dimensionally related PlayRho names.
Definition: AABB.cpp:34
playrho::d2::GetDensity
NonNegative< AreaDensity > GetDensity(const Shape &shape) noexcept
Gets the density of the given shape.
Definition: Shape.hpp:304
playrho::d2::UnitVec::GetRight
static constexpr UnitVec GetRight() noexcept
Gets the right-ward oriented unit vector.
Definition: UnitVec.hpp:74
playrho::d2::ShapeBuilder::UseRestitution
constexpr ConcreteConf & UseRestitution(Finite< Real > value) noexcept
Uses the given restitution.
Definition: ShapeConf.hpp:103
playrho::d2::ShapeBuilder::UseDensity
constexpr ConcreteConf & UseDensity(NonNegative< AreaDensity > value) noexcept
Uses the given density.
Definition: ShapeConf.hpp:111
playrho
Name space for all PlayRho related names.
Definition: AABB.cpp:33
playrho::d2::TestOverlap
Area TestOverlap(const DistanceProxy &proxyA, const Transformation &xfA, const DistanceProxy &proxyB, const Transformation &xfB, DistanceConf conf)
Determine if two generic shapes overlap.
Definition: Distance.cpp:227
playrho::d2::EdgeShapeConf
Edge shape configuration.
Definition: EdgeShapeConf.hpp:42
playrho::d2::CollideShapes
Manifold CollideShapes(const DistanceProxy &shapeA, const Transformation &xfA, const DistanceProxy &shapeB, const Transformation &xfB, Manifold::Conf conf)
Calculates the relevant collision manifold.
Definition: Manifold.cpp:402
playrho::d2::GetFriction
Real GetFriction(const Shape &shape) noexcept
Gets the coefficient of friction.
Definition: Shape.hpp:294
playrho::d2::PolygonShapeConf
Polygon shape configuration.
Definition: PolygonShapeConf.hpp:42
playrho::d2::ShapeBuilder::UseFriction
constexpr ConcreteConf & UseFriction(NonNegative< Real > value) noexcept
Uses the given friction.
Definition: ShapeConf.hpp:95
playrho::d2::GetChild
DistanceProxy GetChild(const ChainShapeConf &arg, ChildCounter index)
Gets the "child" shape for a given chain shape configuration.
Definition: ChainShapeConf.hpp:157
playrho::d2::TypeCast
std::add_pointer_t< std::add_const_t< T > > TypeCast(const Shape *value) noexcept
Converts the given shape into its current configuration value.
Definition: Shape.hpp:610
playrho::ChildCounter
std::remove_const< decltype(MaxChildCount)>::type ChildCounter
Child counter type.
Definition: Settings.hpp:97
playrho::d2::MassData
::playrho::detail::MassData< 2 > MassData
Mass data alias for 2-D objects.
Definition: MassData.hpp:77
playrho::d2::GetVertexRadius
NonNegative< Length > GetVertexRadius(const DistanceProxy &arg) noexcept
Gets the vertex radius property of a given distance proxy.
Definition: DistanceProxy.hpp:205
playrho::GetTypeID
constexpr TypeID GetTypeID()
Gets the type ID for the template parameter type.
Definition: TypeInfo.hpp:113
playrho::InvalidArgument
Invalid argument logic error.
Definition: InvalidArgument.hpp:33
playrho::d2::IsValidShapeType
An "is valid shape type" trait.
Definition: Shape.hpp:518
playrho::Real
float Real
Real-number type.
Definition: Real.hpp:69
playrho::d2::DiskShapeConf::UseRadius
constexpr DiskShapeConf & UseRadius(NonNegative< Length > r) noexcept
Uses the given value as the radius.
Definition: DiskShapeConf.hpp:65
playrho::d2::GetRestitution
Real GetRestitution(const Shape &shape) noexcept
Gets the coefficient of restitution value of the given shape.
Definition: Shape.hpp:299
playrho::Vector
Vector.
Definition: Vector.hpp:49
playrho::d2::GetType
TypeID GetType(const Shape &shape) noexcept
Gets the type info of the use of the given shape.
Definition: Shape.hpp:329
playrho::d2::DiskShapeConf::UseLocation
constexpr DiskShapeConf & UseLocation(Length2 value) noexcept
Uses the given value as the location.
Definition: DiskShapeConf.hpp:58
playrho::d2::Transform
void Transform(ChainShapeConf &arg, const Mat22 &m) noexcept
Transforms the given chain shape configuration's vertices by the given transformation matrix.
Definition: ChainShapeConf.hpp:196
playrho::d2::Transformation
Describes a geometric transformation.
Definition: Transformation.hpp:44
playrho::d2::Shape
Shape.
Definition: Shape.hpp:183
playrho::Length2
Vector2< Length > Length2
2-element vector of Length quantities.
Definition: Vector2.hpp:43
playrho::d2::GetChildCount
ChildCounter GetChildCount(const ChainShapeConf &arg) noexcept
Gets the child count for a given chain shape configuration.
Definition: ChainShapeConf.hpp:151
playrho::d2::DiskShapeConf
Disk shape configuration.
Definition: DiskShapeConf.hpp:42