PlayRho  2.0.0
An interactive physics engine & library.
Shape.cpp

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

/*
* Copyright (c) 2023 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 <any>
#include <chrono>
#include <string>
#include <playrho/d2/Distance.hpp>
#include <playrho/d2/part/Compositor.hpp>
using namespace playrho;
using namespace playrho::d2;
TEST(Shape, Traits)
{
// NOTE: Double parenthesis needed sometimes for proper macro expansion.
EXPECT_TRUE(std::is_default_constructible_v<Shape>);
EXPECT_TRUE(std::is_nothrow_default_constructible_v<Shape>);
EXPECT_FALSE(std::is_trivially_default_constructible_v<Shape>);
// Construction with any 1 supporting argument should succeed...
using X = DiskShapeConf;
EXPECT_TRUE((std::is_constructible_v<Shape, X>));
EXPECT_FALSE((std::is_nothrow_constructible_v<Shape, X>));
EXPECT_FALSE((std::is_trivially_constructible_v<Shape, X>));
// Construction with 2 arguments should fail...
EXPECT_FALSE((std::is_constructible_v<Shape, X, X>));
EXPECT_FALSE((std::is_nothrow_constructible_v<Shape, X, X>));
EXPECT_FALSE((std::is_trivially_constructible_v<Shape, X, X>));
EXPECT_TRUE(std::is_copy_constructible_v<Shape>);
EXPECT_FALSE(std::is_nothrow_copy_constructible_v<Shape>);
EXPECT_FALSE(std::is_trivially_copy_constructible_v<Shape>);
EXPECT_TRUE(std::is_move_constructible_v<Shape>);
EXPECT_TRUE(std::is_nothrow_move_constructible_v<Shape>);
EXPECT_FALSE(std::is_trivially_move_constructible_v<Shape>);
EXPECT_TRUE(std::is_copy_assignable_v<Shape>);
EXPECT_FALSE(std::is_nothrow_copy_assignable_v<Shape>);
EXPECT_FALSE(std::is_trivially_copy_assignable_v<Shape>);
EXPECT_TRUE(std::is_move_assignable_v<Shape>);
EXPECT_TRUE(std::is_nothrow_move_assignable_v<Shape>);
EXPECT_FALSE(std::is_trivially_move_assignable_v<Shape>);
EXPECT_TRUE(std::is_destructible_v<Shape>);
EXPECT_TRUE(std::is_nothrow_destructible_v<Shape>);
EXPECT_FALSE(std::is_trivially_destructible_v<Shape>);
// The value initializing constructor resolves for ineligible types but preferably any such
// instantiation will not actually compile.
EXPECT_TRUE((std::is_constructible_v<Shape, int>));
}
TEST(Shape, DefaultConstruction)
{
EXPECT_EQ(Shape::DefaultDensity, (NonNegative<AreaDensity>{0_kgpm2}));
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_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(Translate(t, Length2{}));
EXPECT_EQ(GetType(s), GetTypeID<void>());
}
namespace {
struct TestConf {
static int defaultConstructorCalled;
static int copyConstructorCalled;
static int moveConstructorCalled;
static int copyAssignmentCalled;
static int moveAssignmentCalled;
static void resetClass()
{
defaultConstructorCalled = 0;
copyConstructorCalled = 0;
moveConstructorCalled = 0;
copyAssignmentCalled = 0;
moveAssignmentCalled = 0;
}
std::string data;
TestConf()
{
++defaultConstructorCalled;
}
TestConf(const TestConf& other): data{other.data}
{
++copyConstructorCalled;
}
TestConf(TestConf&& other): data{std::move(other.data)}
{
++moveConstructorCalled;
}
TestConf& operator=(const TestConf& other)
{
data = other.data;
++copyAssignmentCalled;
return *this;
}
TestConf& operator=(TestConf&& other)
{
data = std::move(other.data);
++moveAssignmentCalled;
return *this;
}
};
int TestConf::defaultConstructorCalled;
int TestConf::copyConstructorCalled;
int TestConf::moveConstructorCalled;
int TestConf::copyAssignmentCalled;
int TestConf::moveAssignmentCalled;
bool operator==(const TestConf& lhs, const TestConf& rhs) noexcept
{
return lhs.data == rhs.data;
}
ChildCounter GetChildCount(const TestConf&) noexcept
{
return 0;
}
DistanceProxy GetChild(const TestConf&, ChildCounter)
{
throw InvalidArgument("not supported");
}
MassData GetMassData(const TestConf&) noexcept
{
return {};
}
NonNegative<Length> GetVertexRadius(const TestConf&, ChildCounter)
{
throw InvalidArgument("not supported");
}
NonNegative<AreaDensity> GetDensity(const TestConf&) noexcept
{
return {};
}
Real GetFriction(const TestConf&) noexcept
{
return {};
}
Real GetRestitution(const TestConf&) noexcept
{
return {};
}
void SetVertexRadius(TestConf&, ChildCounter, NonNegative<Length>)
{
}
Filter GetFilter(const TestConf&) noexcept
{
return {};
}
bool IsSensor(const TestConf&) noexcept
{
return {};
}
static_assert(playrho::d2::detail::IsValidShapeTypeV<TestConf>, "MovableConf must be a valid shape type");
}
TEST(Shape, ConstructionFromMovable)
{
TestConf::resetClass();
ASSERT_FALSE(TestConf::copyConstructorCalled);
ASSERT_FALSE(TestConf::moveConstructorCalled);
TestConf conf;
conf.data = "have some";
Shape s{std::move(conf)};
EXPECT_EQ(std::string(), conf.data);
EXPECT_EQ(0, TestConf::copyConstructorCalled);
EXPECT_EQ(1, TestConf::moveConstructorCalled);
EXPECT_EQ(0, TestConf::copyAssignmentCalled);
EXPECT_EQ(0, TestConf::moveAssignmentCalled);
}
TEST(Shape, AssignmentFromMovable)
{
TestConf::resetClass();
ASSERT_FALSE(TestConf::copyConstructorCalled);
ASSERT_FALSE(TestConf::moveConstructorCalled);
TestConf conf;
conf.data = "have some";
Shape s;
s = std::move(conf);
EXPECT_EQ(std::string(), conf.data);
EXPECT_EQ(0, TestConf::copyConstructorCalled);
EXPECT_EQ(1, TestConf::moveConstructorCalled);
EXPECT_EQ(0, TestConf::copyAssignmentCalled);
EXPECT_EQ(0, TestConf::moveAssignmentCalled);
}
TEST(Shape, ConstructionFromCopyable)
{
TestConf::resetClass();
ASSERT_FALSE(TestConf::copyConstructorCalled);
ASSERT_FALSE(TestConf::moveConstructorCalled);
TestConf conf;
conf.data = "have some";
Shape s{conf};
EXPECT_EQ(std::string("have some"), conf.data);
EXPECT_EQ(1, TestConf::copyConstructorCalled);
EXPECT_EQ(0, TestConf::moveConstructorCalled);
EXPECT_EQ(0, TestConf::copyAssignmentCalled);
EXPECT_EQ(0, TestConf::moveAssignmentCalled);
}
TEST(Shape, AssignmentFromCopyable)
{
TestConf::resetClass();
ASSERT_FALSE(TestConf::copyConstructorCalled);
ASSERT_FALSE(TestConf::moveConstructorCalled);
TestConf conf;
conf.data = "have some";
Shape s;
s = conf;
EXPECT_EQ(std::string("have some"), conf.data);
EXPECT_EQ(1, TestConf::copyConstructorCalled);
EXPECT_EQ(0, TestConf::moveConstructorCalled);
EXPECT_EQ(0, TestConf::copyAssignmentCalled);
EXPECT_EQ(0, TestConf::moveAssignmentCalled);
}
TEST(Shape, SetNoops)
{
TestConf::resetClass();
TestConf conf;
EXPECT_NO_THROW(playrho::d2::detail::SetRestitution(conf, Real(0)));
EXPECT_THROW(playrho::d2::detail::SetRestitution(conf, Real(2)), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::SetFriction(conf, NonNegative<Real>(0)));
EXPECT_THROW(playrho::d2::detail::SetFriction(conf, NonNegative<Real>(2)), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::SetSensor(conf, false));
EXPECT_THROW(playrho::d2::detail::SetSensor(conf, true), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::SetDensity(conf, NonNegative<AreaDensity>()));
EXPECT_THROW(playrho::d2::detail::SetDensity(conf, NonNegative<AreaDensity>(2_kgpm2)), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::SetFilter(conf, Filter()));
EXPECT_THROW(playrho::d2::detail::SetFilter(conf, Filter{0xF, 0xA, 0x7}), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::Translate(conf, Length2()));
EXPECT_THROW(playrho::d2::detail::Translate(conf, Length2{1_m, 2_m}), InvalidArgument);
EXPECT_NO_THROW(playrho::d2::detail::Scale(conf, Vec2(1, 1)));
EXPECT_THROW(playrho::d2::detail::Scale(conf, Vec2{1, 2}), InvalidArgument);
EXPECT_THROW(playrho::d2::detail::Rotate(conf, UnitVec()), InvalidArgument);
}
namespace sans_some {
namespace {
struct ShapeTest {
int number;
};
[[maybe_unused]] ChildCounter GetChildCount(const ShapeTest&)
{
return 1u;
}
[[maybe_unused]] void Translate(ShapeTest&, const Length2&) {}
} // namespace
} // namespace sans_some
TEST(Shape, InitializingConstructor)
{
EXPECT_TRUE((std::is_constructible_v<Shape, ::sans_some::ShapeTest>));
EXPECT_FALSE(playrho::d2::detail::IsValidShapeTypeV<::sans_some::ShapeTest>);
EXPECT_TRUE((std::is_constructible_v<Shape, DiskShapeConf>));
EXPECT_TRUE(playrho::d2::detail::IsValidShapeTypeV<DiskShapeConf>);
auto conf = DiskShapeConf{};
auto s = Shape{conf};
EXPECT_TRUE(s.has_value());
EXPECT_EQ(GetChildCount(s), ChildCounter(1));
EXPECT_EQ(GetFilter(s).categoryBits, Filter{}.categoryBits);
EXPECT_EQ(GetFilter(s).maskBits, Filter{}.maskBits);
EXPECT_EQ(GetFilter(s).groupIndex, Filter{}.groupIndex);
EXPECT_EQ(IsSensor(s), false);
conf.UseIsSensor(true);
s = Shape{conf};
EXPECT_EQ(IsSensor(s), true);
}
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);
s = EdgeShapeConf();
EXPECT_NE(GetType(s), GetTypeID<void>());
EXPECT_EQ(GetType(s), GetTypeID<EdgeShapeConf>());
// Test copy assignment...
const auto otherShape = Shape{};
ASSERT_EQ(GetType(otherShape), GetTypeID<void>());
s = otherShape;
EXPECT_EQ(GetType(s), GetTypeID<void>());
EXPECT_TRUE(s == otherShape);
}
TEST(Shape, TypeCast)
{
const auto shape = Shape{};
EXPECT_THROW(TypeCast<int>(shape), std::bad_cast);
}
TEST(Shape, SetVertexRadius)
{
auto foo = Shape{DiskShapeConf{1_m}};
const auto radius = NonNegative<Length>(0.42_m);
EXPECT_NO_THROW(SetVertexRadius(foo, 0, radius));
EXPECT_EQ(GetVertexRadius(foo, 0), radius);
}
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)
{
using namespace playrho::d2::part;
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>());
EXPECT_NE(GetTypeID(DiskShapeConf{}), GetTypeID(EdgeShapeConf{}));
EXPECT_EQ(GetTypeID(DiskShapeConf{}), GetTypeID(DiskShapeConf{}));
EXPECT_EQ(GetTypeID(EdgeShapeConf{}), GetTypeID(EdgeShapeConf{}));
EXPECT_EQ(GetTypeID(Compositor<GeometryIs<StaticRectangle<1, 1>>>{}),
GetTypeID(Compositor<GeometryIs<StaticRectangle<1, 1>>>{}));
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));
EXPECT_EQ(Shape(Compositor<GeometryIs<StaticRectangle<1, 1>>>{}),
Shape(Compositor<GeometryIs<StaticRectangle<1, 1>>>{}));
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()));
EXPECT_FALSE(Shape(EdgeShapeConf()) == Shape(EdgeShapeConf().UseIsSensor(true)));
const auto filter = Filter{0x2u, 0x8, 0x1};
EXPECT_FALSE(Shape(EdgeShapeConf()) == Shape(EdgeShapeConf().UseFilter(filter)));
}
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 filter = Filter{0x2u, 0x8, 0x1};
EXPECT_TRUE(Shape(EdgeShapeConf()) != Shape(EdgeShapeConf().UseFilter(filter)));
}
TEST(Shape, EmptyShapeTranslateIsNoop)
{
auto s = Shape{};
EXPECT_NO_THROW(Translate(s, Length2{1_m, 2_m}));
}
TEST(Shape, EmptyShapeScaleIsNoop)
{
auto s = Shape{};
EXPECT_NO_THROW(Scale(s, Vec2(Real(2), Real(2))));
}
TEST(Shape, EmptyShapeRotateIsNoop)
{
auto s = Shape{};
EXPECT_NO_THROW(Rotate(s, UnitVec::GetUp()));
}
TEST(Shape, EmptyShapeSetVertexRadiusIsNoop)
{
auto s = Shape{};
EXPECT_NO_THROW(SetVertexRadius(s, 0, 2_m));
}
TEST(Shape, DynamicRectangleSmallerThanPolygon)
{
using namespace playrho::d2::part;
switch (sizeof(Real))
{
#if defined(_WIN64) || !defined(_WIN32)
case 4u:
// Compositor only smaller for 4-byte sized Real values. This is because PolygonShapeConf
// uses std::vector to store vertices and normals. This hides the full amount of memory it
// uses from sizeof(PolygonShapeConf). This also means that not all the data that
// PolygonShapeConf contains is available in one contiguous block of memory which increases
// the chance of memory cache misses that will slow simulations down. Meanwhile, the memory
// for the Compositor used here will be entirely contiguous.
EXPECT_LT(sizeof(Compositor<GeometryIs<DynamicRectangle<>>, //
DensityIs<DynamicAreaDensity<>>, //
RestitutionIs<DynamicRestitution<>>, //
FrictionIs<DynamicFriction<>>, //
SensorIs<DynamicSensor<>>, //
FilterIs<DynamicFilter<>>>),
sizeof(PolygonShapeConf));
break;
#endif
default:
break;
}
}
#if 0
#include <cstddef> // for std::max_align_t
#include <variant>
class Foo {
struct Concept {
virtual ~Concept() = default;
virtual std::unique_ptr<Concept> Clone_() const = 0;
virtual void NewTo_(void* buffer) const = 0;
virtual Real GetFriction_() const noexcept = 0;
};
template <typename T>
struct Model final : Concept {
using data_type = T;
Model(T arg) : data{std::move(arg)} {}
std::unique_ptr<Concept> Clone_() const override
{
return std::make_unique<Model>(data);
}
void NewTo_(void* buffer) const override
{
::new (buffer) Model(data);
}
Real GetFriction_() const noexcept override
{
return GetFriction(data);
}
data_type data;
};
const Concept* GetConcept() const
{
switch (m_type) {
case union_type::pointer:
return m_pointer.get();
case union_type::buffer:
return reinterpret_cast<const Concept*>(::std::addressof(m_buffer));
case union_type::none:
break;
}
return nullptr;
}
public:
#if 1
union {
std::unique_ptr<const Concept> m_pointer;
alignas(std::max_align_t) std::byte m_buffer[alignof(std::max_align_t) * 3u];
};
enum class union_type {none, pointer, buffer};
union_type m_type = union_type::none;
#else
using pointer = std::unique_ptr<const Concept>;
//using buffer = std::aligned_storage_t<16u>;
using buffer = std::byte[62u];
std::variant<pointer, buffer> m_data;
#endif
Foo() noexcept {}
~Foo() noexcept
{
#if 0
if (std::holds_alternative<buffer>(m_data)) {
reinterpret_cast<const Concept*>(&std::get<buffer>(m_data))->~Concept();
}
#endif
switch (m_type) {
case union_type::pointer:
m_pointer.~unique_ptr<const Concept>();
break;
case union_type::buffer:
reinterpret_cast<const Concept*>(m_buffer)->~Concept();
break;
case union_type::none:
break;
}
}
template <class T>
Foo(T&& arg)
{
#if 0
static_assert(alignof(Model<T>) <= alignof(decltype(m_buffer)), "bad alignment");
if (sizeof(Model<T>) > sizeof(m_buffer)) {
new (&m_pointer) std::unique_ptr<const Concept>{std::make_unique<Model<T>>(std::forward<T>(arg))};
m_type = union_type::pointer;
}
else {
new (&m_buffer) Model<T>(std::move(arg));
m_type = union_type::buffer;
}
#else
#if 0
if (sizeof(Model<T>) > sizeof(buffer)) {
m_data = std::unique_ptr<const Concept>{std::make_unique<Model<T>>(std::forward<T>(arg))};
}
else {
buffer b;
m_data = b;
new (&std::get<buffer>(m_data)) Model<T>(std::move(arg));
}
#else
if (sizeof(Model<T>) > sizeof(m_buffer)) {
::new ((void*)(::std::addressof(m_pointer))) std::unique_ptr<const Concept>{std::make_unique<Model<T>>(std::forward<T>(arg))};
m_type = union_type::pointer;
}
else {
::new ((void*)(::std::addressof(m_buffer))) Model<T>(std::move(arg));
m_type = union_type::buffer;
}
#endif
#endif
}
Foo(const Foo& other)
{
switch (other.m_type) {
case union_type::buffer:
reinterpret_cast<const Concept*>(::std::addressof(other.m_buffer))->NewTo_(::std::addressof(m_buffer));
m_type = union_type::buffer;
break;
case union_type::pointer:
::new ((void*)(::std::addressof(m_pointer))) std::unique_ptr<const Concept>{other.m_pointer->Clone_()};
m_type = union_type::pointer;
break;
case union_type::none:
break;
}
}
bool has_value() const noexcept
{
//return m_type != union_type::none;
//return !std::holds_alternative<pointer>(m_data) || std::get<pointer>(m_data);
return m_type == union_type::buffer || (m_type == union_type::pointer && m_pointer);
}
friend Real GetFriction(const Foo& foo) noexcept
{
#if 0
//return foo.has_value()? reinterpret_cast<const Concept*>(&foo.m_buffer)->GetFriction_() : Real(0);
return std::holds_alternative<buffer>(foo.m_data)?
reinterpret_cast<const Concept*>(&std::get<buffer>(foo.m_data))->GetFriction_():
std::get<pointer>(foo.m_data)? std::get<pointer>(foo.m_data)->GetFriction_(): Real(0);
#else
const auto c = foo.GetConcept();
return c? c->GetFriction_(): Real(0);
//return (foo.m_type == union_type::buffer)?
//reinterpret_cast<const Concept*>(foo.m_buffer)->GetFriction_():
//(foo.m_type == union_type::pointer && foo.m_pointer)? foo.m_pointer->GetFriction_(): Real(0);
#endif
}
};
// static_assert(sizeof(Foo) == 64u, "");
struct Foobar {
Real f;
};
static Real GetFriction(const Foobar& value)
{
return value.f;
}
TEST(Shape, TestFoo)
{
EXPECT_EQ(sizeof(Foo), 64u);
EXPECT_FALSE(Foo().has_value());
EXPECT_TRUE(Foo(Foobar{3.0f}).has_value());
EXPECT_EQ(GetFriction(Foo(Foobar{3.0f})), 3.0f);
}
class Concept {
public:
virtual ~Concept() {};
};
struct storage {
union {
std::unique_ptr<const Concept> pointer;
alignas(std::max_align_t) std::byte buffer[alignof(std::max_align_t) * 3u];
};
enum class union_type {pointer, buffer};
union_type type = union_type::pointer;
storage(): pointer{} {}
~storage() {
if (type == union_type::pointer) {
pointer.~unique_ptr<const Concept>();
}
else {
reinterpret_cast<const Concept*>(buffer)->~Concept();
}
}
};
TEST(Shape, TestStorage)
{
storage data;
EXPECT_EQ(sizeof(data), 64u);
EXPECT_EQ(sizeof(data.buffer), 48u);
}
#endif
Definition of the DiskShapeConf class and closely related code.
Definition of the EdgeShapeConf class and closely related code.
Definition of the Manifold class and closely related code.
Definition of the PolygonShapeConf class and closely related code.
Definition of the Shape class and closely related code.
static constexpr auto DefaultDensity
Default density of a default-constructed, or otherwise value-less, shape.
Definition: Shape.hpp:222
static constexpr UnitVec GetUp() noexcept
Gets the up-ward oriented unit vector.
Definition: UnitVec.hpp:128
static constexpr UnitVec GetRight() noexcept
Gets the right-ward oriented unit vector.
Definition: UnitVec.hpp:122
auto Translate(T &, const Length2 &value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasTranslateV< T >, void >
Fallback translate function that throws unless the given value has no effect.
Definition: ShapeModel.hpp:199
auto SetDensity(T &o, NonNegative< AreaDensity > value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasSetDensityV< T >, void >
Fallback density setter that throws unless given the same value as current.
Definition: ShapeModel.hpp:169
auto Scale(T &, const Vec2 &value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasScaleV< T >, void >
Fallback scale function that throws unless the given value has no effect.
Definition: ShapeModel.hpp:209
auto SetFilter(T &o, Filter value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasSetFilterV< T >, void >
Fallback filter setter that throws unless given the same value as current.
Definition: ShapeModel.hpp:189
auto SetFriction(T &o, NonNegative< Real > value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasSetFrictionV< T >, void >
Fallback friction setter that throws unless given the same value as current.
Definition: ShapeModel.hpp:149
auto SetRestitution(T &o, Real value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasSetRestitutionV< T >, void >
Fallback restitution setter that throws unless given the same value as current.
Definition: ShapeModel.hpp:179
auto Rotate(T &, const UnitVec &value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasRotateV< T >, void >
Fallback rotate function that throws unless the given value has no effect.
Definition: ShapeModel.hpp:219
auto SetSensor(T &o, bool value) -> std::enable_if_t< IsValidShapeTypeV< T > &&!HasSetSensorV< T >, void >
Fallback sensor setter that throws unless given the same value as current.
Definition: ShapeModel.hpp:159
Definition: Compositor.hpp:43
Definition: AABB.hpp:48
NonNegative< Length > GetVertexRadius(const ChainShapeConf &arg) noexcept
Gets the vertex radius of the given shape configuration.
Definition: ChainShapeConf.hpp:234
void Rotate(ChainShapeConf &arg, const UnitVec &value)
Rotates the given shape's vertices by the given amount.
Definition: ChainShapeConf.hpp:270
::playrho::detail::MassData< 2 > MassData
Mass data alias for 2-D objects.
Definition: MassData.hpp:88
NonNegative< AreaDensity > GetDensity(const Shape &shape) noexcept
Gets the density of the given shape.
Definition: Shape.hpp:353
NonNegativeFF< Real > GetFriction(const Shape &shape) noexcept
Gets the coefficient of friction.
Definition: Shape.hpp:325
void Scale(ChainShapeConf &arg, const Vec2 &value)
Scales the given shape's vertices by the given amount.
Definition: ChainShapeConf.hpp:264
Filter GetFilter(const Shape &shape) noexcept
Gets the filter value for the given shape.
Definition: Shape.hpp:367
BodyType GetType(const Body &body) noexcept
Gets the type of this body.
Definition: Body.hpp:748
void SetVertexRadius(ChainShapeConf &arg, NonNegative< Length > value) noexcept
Sets the vertex radius of the shape.
Definition: ChainShapeConf.hpp:246
std::add_pointer_t< std::add_const_t< T > > TypeCast(const Joint *value) noexcept
Converts the given joint into its current configuration value.
Definition: Joint.hpp:439
void Translate(ChainShapeConf &arg, const Length2 &value)
Translates the given shape's vertices by the given amount.
Definition: ChainShapeConf.hpp:258
Real GetRestitution(const Shape &shape) noexcept
Gets the coefficient of restitution value of the given shape.
Definition: Shape.hpp:339
bool IsSensor(const Shape &shape) noexcept
Gets whether or not the given shape is a sensor.
Definition: Shape.hpp:381
DistanceProxy GetChild(const ChainShapeConf &arg, ChildCounter index)
Gets the "child" shape for a given chain shape configuration.
Definition: ChainShapeConf.hpp:209
bool operator==(const AabbTreeWorld &lhs, const AabbTreeWorld &rhs)
Equality operator for world comparisons.
Definition: AabbTreeWorld.cpp:927
Manifold CollideShapes(const DistanceProxy &shapeA, const Transformation &xfA, const DistanceProxy &shapeB, const Transformation &xfB, const Manifold::Conf &conf=GetDefaultManifoldConf())
Calculates the relevant collision manifold.
Definition: Manifold.cpp:393
Area TestOverlap(const DistanceProxy &proxyA, const Transformation &xfA, const DistanceProxy &proxyB, const Transformation &xfB, DistanceConf conf=DistanceConf{})
Determine if two generic shapes overlap.
Definition: Distance.cpp:205
MassData GetMassData(const ChainShapeConf &arg)
Gets the mass data for a given chain shape configuration.
Definition: ChainShapeConf.hpp:215
ChildCounter GetChildCount(const ChainShapeConf &arg) noexcept
Gets the child count for a given chain shape configuration.
Definition: ChainShapeConf.hpp:203
Definition: ArrayList.hpp:43
std::remove_const_t< decltype(MaxChildCount)> ChildCounter
Child counter type.
Definition: Settings.hpp:57
float Real
Real-number type.
Definition: Real.hpp:69
TypeID GetTypeID() noexcept
Gets the type ID for the function's template parameter type with its name demangled.
Definition: TypeInfo.hpp:121
auto end(ReversionWrapper< T > w)
End function for getting a reversed order iterator.
Definition: Templates.hpp:118
Vector2< Length > Length2
2-element vector of Length quantities.
Definition: Vector2.hpp:51
Vector2< Real > Vec2
Vector with 2 Real elements.
Definition: Vector2.hpp:47
bits_type categoryBits
The collision category bits.
Definition: Filter.hpp:51
index_type groupIndex
Group index.
Definition: Filter.hpp:61
bits_type maskBits
The collision mask bits.
Definition: Filter.hpp:55