// Copyright Epic Games, Inc. All Rights Reserved. #include "TestNetSerializerFixture.h" #include "Iris/Serialization/QuatNetSerializers.h" #include "Math/UnrealMathUtility.h" #include "Traits/IntType.h" namespace UE::Net { FTestMessage& operator<<(FTestMessage& Message, const FQuat4f& Value) { return Message << Value.ToString(); } FTestMessage& operator<<(FTestMessage& Message, const FQuat4d& Value) { return Message << Value.ToString(); } } namespace UE::Net::Private { static FTestMessage& PrintQuatNetSerializerConfig(FTestMessage& Message, const FNetSerializerConfig& InConfig) { return Message; } template class FTestQuatNetSerializerBase : public TTestNetSerializerFixture { typedef TTestNetSerializerFixture Super; using FloatType = decltype(QuatType::X); using RotatorType = typename Math::TRotator; using UintType = typename TUnsignedIntType::Type; protected: FTestQuatNetSerializerBase(const FNetSerializer& NetSerializer, const FNetSerializerConfig& NetSerializerConfig); void TestIsEqual(); void TestSerialize(FloatType MaxComponentAbsDiff, int32 MaxComponentUlpDiff); void TestValidate(); static FloatType DegToRad(FloatType Value); static FloatType RadToDeg(FloatType Value); static bool SignBit(FloatType Value); static bool IsAlmostEqual(NetSerializerValuePointer Value0, NetSerializerValuePointer Value1, FloatType MaxComponentAbsDiff, int32 MaxComponentUlpDiff); void InitializeClass(); protected: TArray Values; const FNetSerializerConfig& NetSerializerConfig; }; class FTestUnitQuat4fNetSerializer : public FTestQuatNetSerializerBase { typedef FTestQuatNetSerializerBase Super; public: FTestUnitQuat4fNetSerializer(); }; class FTestUnitQuat4dNetSerializer : public FTestQuatNetSerializerBase { typedef FTestQuatNetSerializerBase Super; public: FTestUnitQuat4dNetSerializer(); }; // UnitQuat4fNetSerializer tests UE_NET_TEST_FIXTURE(FTestUnitQuat4fNetSerializer, TestSerialize) { // Tolerate a bit more than 2^-23 difference. constexpr float MaxValueDiff = 0.0000002f; constexpr int32 MaxUlpDiff = 1; TestSerialize(MaxValueDiff, MaxUlpDiff); } UE_NET_TEST_FIXTURE(FTestUnitQuat4fNetSerializer, TestIsEqual) { TestIsEqual(); } UE_NET_TEST_FIXTURE(FTestUnitQuat4fNetSerializer, TestValidate) { TestValidate(); } // UnitQuat4dNetSerializer tests UE_NET_TEST_FIXTURE(FTestUnitQuat4dNetSerializer, TestSerialize) { // Tolerate a bit more than 2^-52 difference. constexpr double MaxValueDiff = 4E-16; constexpr int32 MaxUlpDiff = 1; TestSerialize(MaxValueDiff, MaxUlpDiff); } UE_NET_TEST_FIXTURE(FTestUnitQuat4dNetSerializer, TestIsEqual) { TestIsEqual(); } UE_NET_TEST_FIXTURE(FTestUnitQuat4dNetSerializer, TestValidate) { TestValidate(); } // template FTestQuatNetSerializerBase::FTestQuatNetSerializerBase(const FNetSerializer& NetSerializer, const FNetSerializerConfig& InNetSerializerConfig) : Super(NetSerializer) , NetSerializerConfig(InNetSerializerConfig) { InitializeClass(); } template void FTestQuatNetSerializerBase::TestSerialize(FloatType MaxComponentAbsDiff, int32 MaxComponentUlpDiff) { const auto& EqualityFunc = [MaxComponentAbsDiff, MaxComponentUlpDiff](NetSerializerValuePointer Value0, NetSerializerValuePointer Value1) -> bool { return IsAlmostEqual(Value0, Value1, MaxComponentAbsDiff, MaxComponentUlpDiff); }; for (const bool bQuantizedCompare : {false, true}) { TOptional> CompareFunc; if (!bQuantizedCompare) { CompareFunc = EqualityFunc; } Super::TestSerialize(Values.GetData(), Values.GetData(), Values.Num(), NetSerializerConfig, bQuantizedCompare, CompareFunc); } } template void FTestQuatNetSerializerBase::TestIsEqual() { TArray CompareValues[2]; TArray ExpectedResults[2]; const int32 ValueCount = Values.Num(); { // Make sure nothing messes with our test values. CompareValues[0].SetNumUninitialized(Values.Num()); FPlatformMemory::Memcpy(CompareValues[0].GetData(), Values.GetData(), Values.Num()*sizeof(QuatType)); ExpectedResults[0].Init(true, ValueCount); } { CompareValues[1].SetNumUninitialized(ValueCount); ExpectedResults[1].Init(false, ValueCount); for (int32 ValueIt = 0; ValueIt < ValueCount; ++ValueIt) { // Make sure nothing messes with our test values. FPlatformMemory::Memcpy(CompareValues[1].GetData() + ValueIt, Values.GetData() + ((ValueIt + 1) % ValueCount), sizeof(QuatType)); } } for (SIZE_T TestRoundIt = 0, TestRoundEndIt = 2; TestRoundIt != TestRoundEndIt; ++TestRoundIt) { constexpr bool bQuantizedCompare = true; bool bSuccess = Super::TestIsEqual(Values.GetData(), CompareValues[TestRoundIt].GetData(), ExpectedResults[TestRoundIt].GetData(), ValueCount, NetSerializerConfig, bQuantizedCompare); if (!bSuccess) { return; } } } template void FTestQuatNetSerializerBase::TestValidate() { // Test conforming values { TArray ExpectedResults; ExpectedResults.Init(true, Values.Num()); const bool bSuccess = Super::TestValidate(Values.GetData(), ExpectedResults.GetData(), Values.Num(), NetSerializerConfig); if (!bSuccess) { return; } } } template inline typename FTestQuatNetSerializerBase::FloatType FTestQuatNetSerializerBase::DegToRad(FloatType Value) { return Value*PI/180.0f; } template inline typename FTestQuatNetSerializerBase::FloatType FTestQuatNetSerializerBase::RadToDeg(FloatType Value) { return Value*180.0f/PI; } template inline bool FTestQuatNetSerializerBase::SignBit(FloatType Value) { union FFloatAndUint { FloatType Float; UintType Uint; }; FFloatAndUint FloatToInt; FloatToInt.Float = Value; return FloatToInt.Uint >> (sizeof(UintType)*8U - 1); } template bool FTestQuatNetSerializerBase::IsAlmostEqual(NetSerializerValuePointer Value0, NetSerializerValuePointer Value1, FloatType MaxComponentAbsDiff, int32 MaxComponentUlpDiff) { const QuatType V0 = *reinterpret_cast(Value0); const QuatType V1 = *reinterpret_cast(Value1); const bool bAreUnitQuats = V0.IsNormalized() && V1.IsNormalized(); const bool WSignIsEqual = SignBit(V0.W) == SignBit(V1.W); const FloatType AbsDiffX = FMath::Abs(V0.X - V1.X); const FloatType AbsDiffY = FMath::Abs(V0.Y - V1.Y); const FloatType AbsDiffZ = FMath::Abs(V0.Z - V1.Z); const bool EqualUlpX = FMath::IsNearlyEqualByULP(V0.X, V1.X, MaxComponentUlpDiff); const bool EqualUlpY = FMath::IsNearlyEqualByULP(V0.Y, V1.Y, MaxComponentUlpDiff); const bool EqualUlpZ = FMath::IsNearlyEqualByULP(V0.Z, V1.Z, MaxComponentUlpDiff); if (bAreUnitQuats && WSignIsEqual && ((AbsDiffX <= MaxComponentAbsDiff) || EqualUlpX) && ((AbsDiffY <= MaxComponentAbsDiff) || EqualUlpY) && ((AbsDiffZ <= MaxComponentAbsDiff) || EqualUlpZ)) { return true; } return false; } template void FTestQuatNetSerializerBase::InitializeClass() { Values.Reserve(1024); Values.Add(QuatType(1.0f, 0.0f, 0.0f, 0.0f)); Values.Add(QuatType(-1.0f, 0.0f, 0.0f, 0.0f)); Values.Add(QuatType(0.0f, 1.0f, 0.0f, 0.0f)); Values.Add(QuatType(0.0f, -1.0f, 0.0f, 0.0f)); Values.Add(QuatType(0.0f, 0.0f, 1.0f, 0.0f)); Values.Add(QuatType(0.0f, 0.0f, -1.0f, 0.0f)); Values.Add(QuatType(0.0f, 0.0f, 0.0f, 1.0f)); Values.Add(QuatType(0.0f, 0.0f, 0.0f, -1.0f)); { RotatorType Rotator; const float Angles[] = { 0.0f, 45.0f, 90.0f, 135.0f, 180.0f, 225.0f, 270.0f, 315.0f }; for (const float Pitch : Angles) { Rotator.Pitch = Pitch; for (const float Yaw : Angles) { Rotator.Yaw = Yaw; for (const float Roll : Angles) { Rotator.Roll = Roll; Values.Add(QuatType(Rotator)); } } } } { RotatorType Rotator; Rotator.Pitch = 1.0f; Rotator.Yaw = 2.0f; Rotator.Roll = 3.0f; Values.Add(QuatType(Rotator)); Rotator.Pitch = 357.777777f; Rotator.Yaw = 358.888888f; Rotator.Roll = 359.999999f; Values.Add(QuatType(Rotator)); } } // FTestUnitQuat4fNetSerializer implementation FTestUnitQuat4fNetSerializer::FTestUnitQuat4fNetSerializer() : Super(UE_NET_GET_SERIALIZER(FUnitQuat4fNetSerializer), *UE_NET_GET_SERIALIZER(FUnitQuat4fNetSerializer).DefaultConfig) { } // FTestUnitQuat4dNetSerializer implementation FTestUnitQuat4dNetSerializer::FTestUnitQuat4dNetSerializer() : Super(UE_NET_GET_SERIALIZER(FUnitQuat4dNetSerializer), *UE_NET_GET_SERIALIZER(FUnitQuat4dNetSerializer).DefaultConfig) { } }