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

This is the googletest based unit testing file for the free function interfaces to playrho::d2::World contact member functions and additional functionality.

/*
* 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 "gtest/gtest.h"
using namespace playrho;
using namespace playrho::d2;
TEST(WorldContact, SetAwake)
{
auto world = World{};
const auto s1 = CreateShape(world, DiskShapeConf{});
const auto s2 = CreateShape(world, DiskShapeConf{});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, s1);
Attach(world, bB, s2);
ASSERT_TRUE(GetContacts(world).empty());
Step(world, StepConf{});
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
ASSERT_EQ(GetShapeA(world, c), s1);
ASSERT_EQ(GetShapeB(world, c), s2);
ASSERT_TRUE(IsAwake(world, c));
ASSERT_NO_THROW(UnsetAwake(world, bA));
ASSERT_FALSE(IsAwake(world, bA));
ASSERT_NO_THROW(UnsetAwake(world, bB));
ASSERT_FALSE(IsAwake(world, bB));
EXPECT_NO_THROW(SetAwake(world, c));
EXPECT_TRUE(IsAwake(world, c));
EXPECT_TRUE(IsAwake(world, bA));
EXPECT_TRUE(IsAwake(world, bB));
}
TEST(WorldContact, ResetFriction)
{
const auto shape = DiskShapeConf{};
auto world = World{};
const auto sA = CreateShape(world, Shape{shape});
const auto sB = CreateShape(world, Shape{shape});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, sA);
Attach(world, bB, sB);
ASSERT_TRUE(GetContacts(world).empty());
Step(world, StepConf{});
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
ASSERT_EQ(GetShapeA(world, c), sA);
ASSERT_EQ(GetShapeB(world, c), sB);
ASSERT_GT(GetFriction(shape), Real(0));
ASSERT_NEAR(static_cast<double>(GetFriction(world, c)),
static_cast<double>(Real{GetFriction(shape)}),
0.01);
SetFriction(world, c, GetFriction(shape) * Real(2));
ASSERT_NE(GetFriction(world, c), GetFriction(shape));
ResetFriction(world, c);
EXPECT_NEAR(static_cast<double>(GetFriction(world, c)),
static_cast<double>(Real{GetFriction(shape)}),
0.01);
}
TEST(WorldContact, ResetRestitution)
{
const auto shape = DiskShapeConf{};
auto world = World{};
const auto sA = CreateShape(world, Shape{shape});
const auto sB = CreateShape(world, Shape{shape});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, sA);
Attach(world, bB, sB);
ASSERT_TRUE(GetContacts(world).empty());
Step(world, StepConf{});
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
ASSERT_EQ(GetShapeA(world, c), sA);
ASSERT_EQ(GetShapeB(world, c), sB);
ASSERT_EQ(GetRestitution(shape), Real(0));
ASSERT_EQ(GetRestitution(world, c), GetRestitution(shape));
SetRestitution(world, c, Real(2));
ASSERT_NE(GetRestitution(world, c), GetRestitution(shape));
ResetRestitution(world, c);
EXPECT_EQ(GetRestitution(world, c), GetRestitution(shape));
}
TEST(WorldContact, SetUnsetEnabled)
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, shapeId);
Attach(world, bB, shapeId);
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
EXPECT_NO_THROW(SetEnabled(world, c));
EXPECT_TRUE(IsEnabled(world, c));
EXPECT_NO_THROW(UnsetEnabled(world, c));
EXPECT_FALSE(IsEnabled(world, c));
EXPECT_NO_THROW(SetEnabled(world, c));
EXPECT_TRUE(IsEnabled(world, c));
}
TEST(WorldContact, SetImpenetrable)
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).Use(shapeId));
CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).Use(shapeId));
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
auto contact = GetContact(world, c);
ASSERT_FALSE(IsImpenetrable(contact));
EXPECT_NO_THROW(UnsetImpenetrable(contact));
EXPECT_NO_THROW(SetContact(world, c, contact));
EXPECT_NO_THROW(SetImpenetrable(contact));
EXPECT_THROW(SetContact(world, c, contact), InvalidArgument);
}
TEST(WorldContact, SetSensor)
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).Use(shapeId));
CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).Use(shapeId));
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
auto contact = GetContact(world, c);
ASSERT_FALSE(IsSensor(contact));
EXPECT_NO_THROW(UnsetSensor(contact));
EXPECT_NO_THROW(SetContact(world, c, contact));
EXPECT_NO_THROW(SetSensor(contact));
EXPECT_THROW(SetContact(world, c, contact), InvalidArgument);
}
TEST(WorldContact, SetTangentSpeed)
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, shapeId);
Attach(world, bB, shapeId);
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
const auto c = contacts.begin()->second;
{
const auto linearVelocity = LinearVelocity{5.6_mps};
EXPECT_NO_THROW(SetTangentSpeed(world, c, linearVelocity));
EXPECT_EQ(GetTangentSpeed(world, c), linearVelocity);
}
{
const auto linearVelocity = LinearVelocity{0.2_mps};
EXPECT_NO_THROW(SetTangentSpeed(world, c, linearVelocity));
EXPECT_EQ(GetTangentSpeed(world, c), linearVelocity);
}
}
TEST(WorldContact, WorldManifoldAndMore)
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, shapeId);
Attach(world, bB, shapeId);
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
EXPECT_EQ(GetContactRange(world), 1u);
const auto c = contacts.begin()->second;
auto count = ContactCounter(0);
EXPECT_EQ(count = GetTouchingCount(world), ContactCounter(1));
if (count > ContactCounter(0)) {
auto toi = std::optional<UnitInterval<Real>>{};
EXPECT_NO_THROW(toi = GetToi(world, c));
}
{
auto manifold = WorldManifold{};
EXPECT_NO_THROW(manifold = GetWorldManifold(world, c));
EXPECT_EQ(manifold.GetPointCount(), 1u);
EXPECT_EQ(manifold.GetNormal(), UnitVec::GetRight());
}
EXPECT_EQ(GetToiCount(world, c), 0u);
EXPECT_FALSE(HasValidToi(world, c));
EXPECT_EQ(GetChildIndexA(world, c), ChildCounter(0));
EXPECT_EQ(GetChildIndexB(world, c), ChildCounter(0));
}
TEST(WorldContact, SameTouching)
{
EXPECT_TRUE(SameTouching(World{}, World{}));
{
auto world = World{};
const auto shapeId = CreateShape(world, DiskShapeConf{});
const auto bA = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
const auto bB = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic));
Attach(world, bA, shapeId);
Attach(world, bB, shapeId);
ASSERT_NO_THROW(Step(world, StepConf{}));
const auto contacts = GetContacts(world);
ASSERT_EQ(contacts.size(), ContactCounter(1));
ASSERT_EQ(GetContactRange(world), 1u);
ASSERT_EQ(GetTouchingCount(world), ContactCounter(1));
EXPECT_FALSE(SameTouching(world, World{}));
EXPECT_FALSE(SameTouching(World{}, world));
}
}
Declarations of BodyConf class & free functions associated with it.
Definition of the Contact class and closely related code.
Definition of the DiskShapeConf class and closely related code.
Declarations of the StepConf class, and free functions associated with it.
Declarations of free functions of World for bodies identified by BodyID.
Declarations of free functions of World for contacts identified by ContactID.
Declarations of free functions of World for unidentified information.
Declarations of free functions of World for shapes identified by ShapeID.
Definitions of the World class and closely related code.
static constexpr UnitVec GetRight() noexcept
Gets the right-ward oriented unit vector.
Definition: UnitVec.hpp:122
detail::velocity LinearVelocity
Linear velocity quantity.
Definition: Units.hpp:253
Definition: AABB.hpp:48
std::optional< UnitIntervalFF< Real > > GetToi(const World &world, ContactID id)
Gets the time of impact (TOI) as a fraction or empty value.
Definition: WorldContact.cpp:109
WorldManifold GetWorldManifold(const World &world, ContactID id)
Gets the world manifold for the identified contact.
Definition: WorldContact.cpp:181
auto SameTouching(const World &lhs, const World &rhs) -> bool
Determines whether the given worlds have the same touching contacts & manifolds.
Definition: WorldContact.cpp:213
bool IsAwake(const Body &body) noexcept
Gets the awake/asleep state of this body.
Definition: Body.hpp:884
NonNegativeFF< Real > GetFriction(const Shape &shape) noexcept
Gets the coefficient of friction.
Definition: Shape.hpp:325
ContactCounter GetContactRange(const AabbTreeWorld &world) noexcept
Gets the extent of the currently valid contact range.
Definition: AabbTreeWorld.cpp:1014
void UnsetImpenetrable(Body &body) noexcept
Unsets the impenetrable status of this body.
Definition: Body.hpp:825
BodyID CreateBody(AabbTreeWorld &world, Body body=Body{})
Creates a rigid body that's a copy of the given one.
Definition: AabbTreeWorld.cpp:1019
void SetRestitution(Shape &shape, Real value)
Sets the coefficient of restitution value of the given shape.
Definition: Shape.hpp:344
ShapeID CreateShape(AabbTreeWorld &world, Shape def)
Creates an identifiable copy of the given shape within this world.
Definition: AabbTreeWorld.cpp:1234
void UnsetEnabled(Body &body) noexcept
Unsets the enabled state.
Definition: Body.hpp:866
void SetAwake(Body &body) noexcept
Awakens this body.
Definition: Body.hpp:898
void SetFriction(Shape &shape, NonNegative< Real > value)
Sets the coefficient of friction.
Definition: Shape.hpp:330
void Attach(AabbTreeWorld &world, BodyID id, ShapeID shapeID)
Associates a validly identified shape with the validly identified body.
Definition: AabbTreeWorld.cpp:2896
Real GetRestitution(const Shape &shape) noexcept
Gets the coefficient of restitution value of the given shape.
Definition: Shape.hpp:339
void ResetFriction(World &world, ContactID id)
Definition: WorldContact.hpp:209
bool IsSensor(const Shape &shape) noexcept
Gets whether or not the given shape is a sensor.
Definition: Shape.hpp:381
bool HasValidToi(const World &world, ContactID id)
Whether or not the contact has a valid TOI.
Definition: WorldContact.cpp:104
const BodyContactIDs & GetContacts(const AabbTreeWorld &world, BodyID id)
Gets the contacts associated with the identified body.
Definition: AabbTreeWorld.cpp:2554
ContactCounter GetTouchingCount(const World &world)
Gets the touching count for the given world.
Definition: WorldContact.cpp:186
bool IsImpenetrable(const Body &body) noexcept
Is this body treated like a bullet for continuous collision detection?
Definition: Body.hpp:805
ShapeID GetShapeA(const World &world, ContactID id)
Gets shape A of the identified contact.
Definition: WorldContact.cpp:69
void SetContact(AabbTreeWorld &world, ContactID id, Contact value)
Sets the identified contact's state.
Definition: AabbTreeWorld.cpp:2797
void SetImpenetrable(Body &body) noexcept
Sets the impenetrable status of this body.
Definition: Body.hpp:815
LinearVelocity GetTangentSpeed(const World &world, ContactID id)
Gets the tangent speed of the identified contact.
Definition: WorldContact.cpp:138
ChildCounter GetChildIndexB(const World &world, ContactID id)
Gets the child primitive index B for the identified contact.
Definition: WorldContact.cpp:64
const Contact & GetContact(const AabbTreeWorld &world, ContactID id)
Gets the identified contact.
Definition: AabbTreeWorld.cpp:2860
bool IsEnabled(const Body &body) noexcept
Gets the enabled/disabled state of the body.
Definition: Body.hpp:850
void SetEnabled(Body &body) noexcept
Sets the enabled state.
Definition: Body.hpp:858
void SetTangentSpeed(World &world, ContactID id, LinearVelocity value)
Sets the desired tangent speed for a conveyor belt behavior.
Definition: WorldContact.cpp:143
void SetSensor(Shape &shape, bool value)
Sets whether or not the given shape is a sensor.
Definition: Shape.hpp:386
ShapeID GetShapeB(const World &world, ContactID id)
Gets shape B of the identified contact.
Definition: WorldContact.cpp:74
ChildCounter GetChildIndexA(const World &world, ContactID id)
Gets the child primitive index A for the identified contact.
Definition: WorldContact.cpp:59
TimestepIters GetToiCount(const World &world, ContactID id)
Gets the Time Of Impact (TOI) count.
Definition: WorldContact.cpp:89
StepStats Step(AabbTreeWorld &world, const StepConf &conf)
Steps the world simulation according to the given configuration.
Definition: AabbTreeWorld.cpp:2144
void ResetRestitution(World &world, ContactID id)
Definition: WorldContact.hpp:217
void UnsetAwake(Body &body) noexcept
Sets this body to asleep if sleeping is allowed.
Definition: Body.hpp:913
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
constexpr void UnsetSensor(Contact &contact) noexcept
Unsets the sensor state of the given contact.
Definition: Contact.hpp:687
WiderType< BodyCounter > ContactCounter
Count type for contacts.
Definition: Settings.hpp:193
constexpr bool empty(IndexPair3 pairs) noexcept
Checks whether the given collection of index pairs is empty.
Definition: IndexPair.hpp:75