/
spivag
/
command_occ
Обзор
Документация
Войти
/
spivag
/
command_occ
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
initial
tests/gtest_r2.cpp
1 115 строк
41 KB
Spivakov
Initial
10 апр 2026, 13:45
10 апр 2026, 13:45
b2ad868
Код
Авторство
О чём код?
/// @file gtest_r2.cpp /// @brief R2 primitives tests /// @project gtests /// /// (c) Alexander Spivakov on 02.09.2025. /// #include <geometry_types.h> #include <c3d_bridge.hxx> #include <gtest/gtest.h> #include <cmath> TEST(R2Point, DefaultCts) { const R2::Point pnt{}; ASSERT_NEAR(0., pnt.x(), R1::Distance::Epsilon()); ASSERT_NEAR(0., pnt.y(), R1::Distance::Epsilon()); } TEST(R2Point, CartesianCtor) { const R2::Point pnt{7., -40.8}; ASSERT_NEAR(7., pnt.x(), R1::Distance::Epsilon()); ASSERT_NEAR(-40.8, pnt.y(), R1::Distance::Epsilon()); } TEST( R2Point, CopyCtor ) { const R2::Point pnt{ 7., -40.8 }; const R2::Point pntCopy{pnt}; const R2::Point pntAssign = pnt; ASSERT_NEAR( pntCopy.x(), pnt.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( pntCopy.y(), pnt.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( pntAssign.x(), pnt.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( pntAssign.y(), pnt.y(), R1::Distance::Epsilon() ); } TEST( R2Point, Move ) { R2::Point p{}; const R2::Point pInitial{p}; p += R2::Vector::ox(); ASSERT_NEAR( 1., p.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., p.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., pInitial.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., pInitial.y(), R1::Distance::Epsilon() ); } TEST( R2Point, Rotate ) { R2::Point p{ R2::Point::Polar{ R1::Distance{ 10. }, R1::Angle02pi{ R1::Angle::Degrees{ 45. } } } }; const R2::Point pCenter{ R2::Point::Polar{ R1::Distance{ 5. }, R1::Angle02pi{ R1::Angle::Degrees{ 45. } } } }; p.RotateCcw( pCenter, R1::Angle{ R1::Angle::Degrees{ 180. } } ); ASSERT_NEAR( 0., p.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., p.y(), R1::Distance::Epsilon() ); } TEST(R2Point, PolarCtor) { const R2::Point pnt{ R2::Point::Polar{ R1::Distance{3.}, R1::Angle02pi(R1::Angle::Degrees{120.}) } }; ASSERT_NEAR(-1.5, pnt.x(), R1::Distance::Epsilon()); ASSERT_NEAR(1.5 * sqrt(3.), pnt.y(), R1::Distance::Epsilon()); } TEST(R2Vector, DefaultCts) { const R2::Vector vec{}; ASSERT_NEAR(0., vec.x(), R1::Distance::Epsilon()); ASSERT_NEAR(0., vec.y(), R1::Distance::Epsilon()); } TEST(R2Vector, CartesianCtor) { const R2::Vector vec{7., -40.8}; ASSERT_NEAR(7., vec.x(), R1::Distance::Epsilon()); ASSERT_NEAR(-40.8, vec.y(), R1::Distance::Epsilon()); } TEST( R2Vector, CopyCtor ) { const R2::Vector vec{ 7., -40.8 }; R2::Vector r2vecCopy{ vec }; R2::Vector r2vecAssign = vec; ASSERT_NEAR( r2vecCopy.x(), vec.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( r2vecCopy.y(), vec.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( r2vecAssign.x(), vec.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( r2vecAssign.y(), vec.y(), R1::Distance::Epsilon() ); } TEST(R2Vector, PolarCtor) { R2::Vector vec{ R1::Angle{R1::Angle::Degrees{150.}} }; ASSERT_NEAR( -0.5 * sqrt(3.), vec.x(), R1::Distance::Epsilon()); ASSERT_NEAR( 0.5, vec.y(), R1::Distance::Epsilon()); } TEST(R2Vector, PointsCtor) { R2::StartPoint startPt{11., -3.}; R2::EndPoint endPt{ -6., -5.5 }; R2::Vector vec{ startPt, endPt }; ASSERT_NEAR(-17., vec.x(), R1::Distance::Epsilon()); ASSERT_NEAR(-2.5, vec.y(), R1::Distance::Epsilon()); } TEST( R2Vector, Move ) { R2::Vector p{ 5., 2. }; R2::Vector pInitial{ p }; p += R2::Vector::ox(); ASSERT_NEAR( 6., p.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., p.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 5., pInitial.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., pInitial.y(), R1::Distance::Epsilon() ); } TEST( R2Vector, Rotate ) { R2::Point p{ R2::Point::Polar{ R1::Distance{ 10. }, R1::Angle02pi{ R1::Angle::Degrees{ 45. } } } }; const R2::Point pCenter{ R2::Point::Polar{ R1::Distance{ 5. }, R1::Angle02pi{ R1::Angle::Degrees{ 45. } } } }; p.RotateCcw( pCenter, R1::Angle{ R1::Angle::Degrees{ 180. } } ); ASSERT_NEAR( 0., p.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., p.y(), R1::Distance::Epsilon() ); } TEST(R2Placement, DefaultCtor) { R2::Placement place{}; ASSERT_NEAR( 0., place.Origin().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., place.Origin().y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., place.Axis().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., place.Axis().y(), R1::Distance::Epsilon() ); } TEST( R2Placement, CtorPoint ) { const R2::Point center{ -11., 21.2 }; R2::Placement place{ center }; ASSERT_NEAR( center.x(), place.Origin().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( center.y(), place.Origin().y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., place.Axis().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., place.Axis().y(), R1::Distance::Epsilon() ); } TEST( R2Placement, CtorPointAngle ) { const R2::Point center{ -11., 21.2 }; R2::Placement place{ center, R1::Angle{ R1::Angle::Degrees{45.} } }; ASSERT_NEAR( center.x(), place.Origin().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( center.y(), place.Origin().y(), R1::Distance::Epsilon() ); ASSERT_NEAR( sqrt(0.5), place.Axis().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( sqrt( 0.5 ), place.Axis().y(), R1::Distance::Epsilon() ); } TEST( R2Placement, Move ) { R2::Placement place{ {}, R1::Angle{ R1::Angle::Degrees{ 45. } } }; R2::Vector center{ 3., -56. }; place += center; ASSERT_NEAR( center.x(), place.Origin().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( center.y(), place.Origin().y(), R1::Distance::Epsilon() ); ASSERT_NEAR( sqrt( 0.5 ), place.Axis().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( sqrt( 0.5 ), place.Axis().y(), R1::Distance::Epsilon() ); } TEST( R2Placement, Rotate ) { R2::Placement place{ R2::Point{ 1., 0. }, R1::Angle{ R1::Angle::Degrees{ 45. } } }; R2::Point center{ 1., 1. }; place.RotateCcw( center, R1::Angle{ R1::Angle::Degrees{90.} } ); ASSERT_NEAR( 2., place.Origin().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., place.Origin().y(), R1::Distance::Epsilon() ); ASSERT_NEAR( -sqrt( 0.5 ), place.Axis().x(), R1::Distance::Epsilon() ); ASSERT_NEAR( sqrt( 0.5 ), place.Axis().y(), R1::Distance::Epsilon() ); } TEST( R2_Gabarit, DefaultConstructor ) { R2::Gabarit r{}; ASSERT_TRUE( r.IsEmpty() ); ASSERT_FALSE( r.PointIn( R2::Point{} ) ); } //TEST( R2_Gabarit, ConstructorOneValue ) //{ // for ( double value = -10.; value <= 10.0; value += 5.0 ) // { // R2::Gabarit r{ value }; // ASSERT_TRUE( r.Contains( value ) ); // ASSERT_FALSE( r.Contains( value + R1::Distance::Epsilon() ) ); // ASSERT_FALSE( r.Contains( value - R1::Distance::Epsilon() ) ); // ASSERT_FALSE( r.IsEmpty() ); // } //} // //TEST( R1_Range, ConstructorTwoValuesAscending ) //{ // R1::Range r{ 0., 10. }; // ASSERT_TRUE( r.Contains( 3. ) ); // ASSERT_FALSE( r.Contains( 12. ) ); // ASSERT_FALSE( r.Contains( -120. ) ); // ASSERT_FALSE( r.IsEmpty() ); //} // //TEST( R1_Range, ConstructorTwoValuesDescending ) //{ // R1::Range r{ 10., 3. }; // ASSERT_TRUE( r.Contains( 3. ) ); // ASSERT_FALSE( r.Contains( 12. ) ); // ASSERT_FALSE( r.Contains( -120. ) ); // ASSERT_FALSE( r.IsEmpty() ); //} TEST( R2_Gabarit, Empty ) { R2::Gabarit r; ASSERT_TRUE( r.IsEmpty() ); r.Append( R2::Point{3., 3.} ); ASSERT_FALSE( r.IsEmpty() ); r.SetEmpty(); ASSERT_TRUE( r.IsEmpty() ); } TEST( R2_Gabarit, InterpositionNotAppliable ) { const R2::Gabarit r1{}, r2{ R2::Point{} }; ASSERT_EQ( Interposition::notAppliable, r1.InterpositionWith( r2 ) ); ASSERT_EQ( Interposition::notAppliable, r2.InterpositionWith( r1 ) ); ASSERT_EQ( Interposition::notAppliable, r1.InterpositionWith( r1 ) ); ASSERT_NE( Interposition::notAppliable, r2.InterpositionWith( r2 ) ); } TEST( R2_Gabarit, InterpositionSame ) { const R2::Gabarit r1{ R2::Point{}, R2::Point{} }, r2{ R2::Point{} }; ASSERT_EQ( Interposition::same, r1.InterpositionWith( r2 ) ); ASSERT_EQ( Interposition::same, r2.InterpositionWith( r1 ) ); } TEST( R2_Gbabrit, InterpositionConainsIncluded ) { const R2::Gabarit r1{ R2::Point{ -10., -10. }, R2::Point{ 10., 10. } }, r2{ R2::Point{}, R2::Point{ 1., 1. } }; ASSERT_EQ( Interposition::contains, r1.InterpositionWith( r2 ) ); ASSERT_EQ( Interposition::included, r2.InterpositionWith( r1 ) ); } TEST( R2_Gabarit, InterpositionIntersect ) { const R2::Gabarit r1{ R2::Point{ -10., -10. }, R2::Point{ 10., 10. } }, r2{ R2::Point{}, R2::Point{ 18., 16. } }; ASSERT_EQ( Interposition::intersected, r1.InterpositionWith( r2 ) ); ASSERT_EQ( Interposition::intersected, r2.InterpositionWith( r1 ) ); } TEST( R2_Gabarit, InterpositionIsolated ) { const R2::Gabarit r1{ R2::Point{ -10., -10. }, R2::Point{ 10., 10. } }, r2{ R2::Point{ 16., 40. }, R2::Point{ 18., 20. } }; ASSERT_EQ( Interposition::isolated, r1.InterpositionWith( r2 ) ); ASSERT_EQ( Interposition::isolated, r2.InterpositionWith( r1 ) ); } // ============= Transform tests ============= TEST( R2Transform, DefaultConstructor ) { const R2::Transform trns{}; // Проверяем, что по умолчанию это тождественное преобразование ASSERT_NEAR( 0., trns.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., trns.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., trns.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, TranslationConstructor ) { const R2::Point origin{ 5., -3. }; const R2::Transform trns{ origin }; ASSERT_NEAR( 5., trns.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( -3., trns.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., trns.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., trns.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, FullConstructor ) { const R2::Point origin{ 1., 2. }; const R2::Vector ox{ 2., 0. }; const R2::Vector oy{ 0., 3. }; const R2::Transform trns{ origin, ox, oy }; ASSERT_NEAR( 1., trns.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., trns.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., trns.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., trns.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 3., trns.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, World ) { const R2::Transform world = R2::Transform::World(); ASSERT_NEAR( 0., world.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., world.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., world.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., world.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., world.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., world.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, MultiplyByIdentity ) { const R2::Point origin{ 3., 4. }; const R2::Vector ox{ 1., 1. }; const R2::Vector oy{ -1., 1. }; const R2::Transform trns{ origin, ox, oy }; const R2::Transform identity = R2::Transform::World(); const R2::Transform result = trns * identity; ASSERT_NEAR( origin.x(), result.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( origin.y(), result.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( ox.x(), result.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( ox.y(), result.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( oy.x(), result.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( oy.y(), result.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, MultiplyTranslations ) { // Два последовательных сдвига const R2::Transform trns1{ R2::Point{ 2., 3. } }; const R2::Transform trns2{ R2::Point{ 1., -1. } }; const R2::Transform result = trns1 * trns2; // Результат должен быть сдвигом на (2+1, 3-1) = (3, 2) ASSERT_NEAR( 3., result.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.m_origin.y(), R1::Distance::Epsilon() ); // Базисные векторы должны остаться теми же (для чистого сдвига) ASSERT_NEAR( 1., result.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., result.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, MultiplyScaling ) { // Масштабирование вдоль осей const R2::Vector ox1{ 2., 0. }; const R2::Vector oy1{ 0., 3. }; const R2::Transform scale1{ R2::Point{}, ox1, oy1 }; const R2::Vector ox2{ 2., 0. }; const R2::Vector oy2{ 0., 0.5 }; const R2::Transform scale2{ R2::Point{}, ox2, oy2 }; const R2::Transform result = scale1 * scale2; // Результирующее преобразование должно иметь ox = (4, 0) и oy = (0, 1.5) ASSERT_NEAR( 4., result.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1.5, result.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, MultiplyTranslationAndScaling ) { // Сдвиг на (1, 2) const R2::Transform translation{ R2::Point{ 1., 2. } }; // Масштабирование на (2, 2) const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Transform result = translation * scaling; // После применения сначала масштабирования, потом сдвига // origin = translation.origin + translation.ox * scaling.origin.x + translation.oy * scaling.origin.y // = (1, 2) + (1, 0) * 0 + (0, 1) * 0 = (1, 2) ASSERT_NEAR( 1., result.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.m_origin.y(), R1::Distance::Epsilon() ); // ox = translation.ox * scaling.ox.x + translation.oy * scaling.ox.y // = (1, 0) * 2 + (0, 1) * 0 = (2, 0) ASSERT_NEAR( 2., result.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_ox.y(), R1::Distance::Epsilon() ); // oy = translation.ox * scaling.oy.x + translation.oy * scaling.oy.y // = (1, 0) * 0 + (0, 1) * 2 = (0, 2) ASSERT_NEAR( 0., result.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, MultiplyComplexTransforms ) { // Первое преобразование: сдвиг + поворот на 90 градусов const R2::Transform trns1{ R2::Point{ 1., 1. }, R2::Vector{ 0., 1. }, // ox повёрнут на 90 градусов R2::Vector{ -1., 0. } // oy повёрнут на 90 градусов }; // Второе преобразование: сдвиг и масштабирование const R2::Transform trns2{ R2::Point{ 2., 0. }, R2::Vector{ 2., 0. }, // ox масштабирован на 2 R2::Vector{ 0., 1. } // oy без масштабирования }; const R2::Transform result = trns1 * trns2; // Проверяем результирующее преобразование // origin = (1, 1) + (0, 1) * 2 + (-1, 0) * 0 = (1, 3) ASSERT_NEAR( 1., result.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 3., result.m_origin.y(), R1::Distance::Epsilon() ); // ox = (0, 1) * 2 + (-1, 0) * 0 = (0, 2) ASSERT_NEAR( 0., result.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.m_ox.y(), R1::Distance::Epsilon() ); // oy = (0, 1) * 0 + (-1, 0) * 1 = (-1, 0) ASSERT_NEAR( -1., result.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 0., result.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, CopyConstructor ) { const R2::Transform original{ R2::Point{ 5., -2. }, R2::Vector{ 1.5, 0.5 }, R2::Vector{ -0.5, 1.5 } }; const R2::Transform copy{ original }; ASSERT_NEAR( original.m_origin.x(), copy.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), copy.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.x(), copy.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.y(), copy.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.x(), copy.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.y(), copy.m_oy.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, AssignmentOperator ) { const R2::Transform original{ R2::Point{ 3., 4. }, R2::Vector{ 0., 1. }, R2::Vector{ -1., 0. } }; R2::Transform assigned; assigned = original; ASSERT_NEAR( original.m_origin.x(), assigned.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), assigned.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.x(), assigned.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.y(), assigned.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.x(), assigned.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.y(), assigned.m_oy.y(), R1::Distance::Epsilon() ); } // ============= Apply tests ============= TEST( R2Transform, ApplyPointIdentity ) { const R2::Transform identity = R2::Transform::World(); const R2::Point point{ 3., 4. }; const R2::Point result = identity.Apply( point ); // Тождественное преобразование не меняет точку ASSERT_NEAR( 3., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 4., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyPointTranslation ) { const R2::Transform translation{ R2::Point{ 2., 3. } }; const R2::Point point{ 1., -1. }; const R2::Point result = translation.Apply( point ); // Результат = origin + point = (2, 3) + (1, -1) = (3, 2) ASSERT_NEAR( 3., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyPointScaling ) { const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 3. } }; const R2::Point point{ 2., 3. }; const R2::Point result = scaling.Apply( point ); // Результат = (2*2, 3*3) = (4, 9) ASSERT_NEAR( 4., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 9., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyPointScalingWithTranslation ) { const R2::Transform transform{ R2::Point{ 1., 2. }, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Point point{ 1., 1. }; const R2::Point result = transform.Apply( point ); // Результат = origin + ox * x + oy * y // = (1, 2) + (2, 0) * 1 + (0, 2) * 1 // = (1, 2) + (2, 0) + (0, 2) // = (3, 4) ASSERT_NEAR( 3., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 4., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyPointRotation ) { // Поворот на 90 градусов CCW const R2::Transform rotation{ R2::Point{}, R2::Vector{ 0., 1. }, // ox повёрнут на 90° R2::Vector{ -1., 0. } // oy повёрнут на 90° }; const R2::Point point{ 1., 0. }; const R2::Point result = rotation.Apply( point ); // Точка (1, 0) повёрнута на 90° CCW должна быть (0, 1) ASSERT_NEAR( 0., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyVectorIdentity ) { const R2::Transform identity = R2::Transform::World(); const R2::Vector r2ver{ 5., -2. }; const R2::Vector result = identity.Apply( r2ver ); // Тождественное преобразование не меняет вектор ASSERT_NEAR( 5., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( -2., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyVectorTranslation ) { // Трансляция НЕ влияет на вектор (только на точку) const R2::Transform translation{ R2::Point{ 10., 20. } }; const R2::Vector r2ver{ 3., 4. }; const R2::Vector result = translation.Apply( r2ver ); // Вектор остаётся без изменений ASSERT_NEAR( 3., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 4., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyVectorScaling ) { const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 0.5 } }; const R2::Vector r2ver{ 2., 4. }; const R2::Vector result = scaling.Apply( r2ver ); // Результат = ox * x + oy * y // = (2, 0) * 2 + (0, 0.5) * 4 // = (4, 0) + (0, 2) // = (4, 2) ASSERT_NEAR( 4., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 2., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyVectorRotation ) { // Поворот на 90 градусов CCW const R2::Transform rotation{ R2::Point{ 5., 5. }, // origin не влияет на вектор R2::Vector{ 0., 1. }, // ox повёрнут на 90° R2::Vector{ -1., 0. } // oy повёрнут на 90° }; const R2::Vector r2ver{ 1., 0. }; const R2::Vector result = rotation.Apply( r2ver ); // Вектор (1, 0) повёрнут на 90° CCW должен быть (0, 1) ASSERT_NEAR( 0., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ApplyVectorComplex ) { const R2::Transform transform{ R2::Point{ 100., 100. }, // не влияет на вектор R2::Vector{ 2., 1. }, R2::Vector{ -1., 2. } }; const R2::Vector r2ver{ 1., 1. }; const R2::Vector result = transform.Apply( r2ver ); // Результат = (2, 1) * 1 + (-1, 2) * 1 = (2, 1) + (-1, 2) = (1, 3) ASSERT_NEAR( 1., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 3., result.y(), R1::Distance::Epsilon() ); } // ============= Displacement Tests for MbPlacement Integration ============= TEST( R2Transform, DisplacementWithUnitBasis ) { // Простое смещение с единичными базисными векторами // Это должно работать с MbPlacement без проблем const R2::Transform displacement{ R2::Point{ 5., 7. }, R2::Vector{ 1., 0. }, R2::Vector{ 0., 1. } }; const R2::Point point{ 2., 3. }; const R2::Point result = displacement.Apply( point ); // Результат = (5, 7) + (1, 0) * 2 + (0, 1) * 3 = (5, 7) + (2, 3) = (7, 10) ASSERT_NEAR( 7., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 10., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, DisplacementPreservesDistance ) { // Проверка, что смещение не меняет расстояния между точками const R2::Transform displacement{ R2::Point{ 10., -5. } }; const R2::Point p1{ 1., 2. }; const R2::Point p2{ 4., 6. }; const R2::Point transformed_p1 = displacement.Apply( p1 ); const R2::Point transformed_p2 = displacement.Apply( p2 ); // Исходное расстояние const double original_dist_sq = (p2.x() - p1.x()) * (p2.x() - p1.x()) + (p2.y() - p1.y()) * (p2.y() - p1.y()); // Расстояние после смещения const double transformed_dist_sq = (transformed_p2.x() - transformed_p1.x()) * (transformed_p2.x() - transformed_p1.x()) + (transformed_p2.y() - transformed_p1.y()) * (transformed_p2.y() - transformed_p1.y()); ASSERT_NEAR( original_dist_sq, transformed_dist_sq, R1::Distance::Epsilon() ); } TEST( R2Transform, DisplacementComposition ) { // Композиция двух смещений должна быть эквивалентна одному смещению const R2::Transform disp1{ R2::Point{ 3., 4. } }; const R2::Transform disp2{ R2::Point{ 2., -1. } }; const R2::Transform composed = disp1 * disp2; const R2::Point point{ 1., 1. }; // Применение композиции const R2::Point result_composed = composed.Apply( point ); // Применение последовательно: сначала disp2, потом disp1 const R2::Point temp = disp2.Apply( point ); const R2::Point result_sequential = disp1.Apply( temp ); ASSERT_NEAR( result_sequential.x(), result_composed.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( result_sequential.y(), result_composed.y(), R1::Distance::Epsilon() ); } // ============= Rotation Tests for MbPlacement Integration ============= TEST( R2Transform, Rotation90Degrees ) { // Поворот на 90 градусов против часовой стрелки const R2::Transform rotation{ R2::Point{}, R2::Vector{ 0., 1. }, R2::Vector{ -1., 0. } }; const R2::Point point{ 1., 0. }; const R2::Point result = rotation.Apply( point ); ASSERT_NEAR( 0., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 1., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, Rotation45Degrees ) { // Поворот на 45 градусов против часовой стрелки const double cos45 = std::sqrt( 2. ) / 2.; const double sin45 = std::sqrt( 2. ) / 2.; const R2::Transform rotation{ R2::Point{}, R2::Vector{ cos45, sin45 }, R2::Vector{ -sin45, cos45 } }; const R2::Point point{ 1., 0. }; const R2::Point result = rotation.Apply( point ); // Точка (1, 0) повёрнута на 45° должна быть (cos45, sin45) ASSERT_NEAR( cos45, result.x(), 1e-10 ); ASSERT_NEAR( sin45, result.y(), 1e-10 ); } TEST( R2Transform, RotationPreservesDistance ) { // Проверка, что поворот не меняет расстояния (изометрия) const double angle = M_PI / 6; // 30 градусов const double cos_a = std::cos( angle ); const double sin_a = std::sin( angle ); const R2::Transform rotation{ R2::Point{}, R2::Vector{ cos_a, sin_a }, R2::Vector{ -sin_a, cos_a } }; const R2::Point p1{ 3., 4. }; const R2::Point p2{ 5., 1. }; const R2::Point rot_p1 = rotation.Apply( p1 ); const R2::Point rot_p2 = rotation.Apply( p2 ); // Исходное расстояние const double dx = p2.x() - p1.x(); const double dy = p2.y() - p1.y(); const double original_dist = std::sqrt( dx * dx + dy * dy ); // Расстояние после поворота const double rot_dx = rot_p2.x() - rot_p1.x(); const double rot_dy = rot_p2.y() - rot_p1.y(); const double rotated_dist = std::sqrt( rot_dx * rot_dx + rot_dy * rot_dy ); ASSERT_NEAR( original_dist, rotated_dist, 1e-10 ); } TEST( R2Transform, RotationComposition ) { // Композиция двух поворотов на 45° должна быть поворотом на 90° const double cos45 = std::sqrt( 2. ) / 2.; const double sin45 = std::sqrt( 2. ) / 2.; const R2::Transform rot45{ R2::Point{}, R2::Vector{ cos45, sin45 }, R2::Vector{ -sin45, cos45 } }; const R2::Transform rot90 = rot45 * rot45; const R2::Point point{ 1., 0. }; const R2::Point result = rot90.Apply( point ); // Результат должен быть эквивалентен повороту на 90° (примерно (0, 1)) ASSERT_NEAR( 0., result.x(), 1e-10 ); ASSERT_NEAR( 1., result.y(), 1e-10 ); } TEST( R2Transform, DisplacementAndRotation ) { // Комбинированное преобразование: смещение + поворот const double cos90 = 0.; const double sin90 = 1.; const R2::Transform transform{ R2::Point{ 5., 5. }, // смещение центра поворота R2::Vector{ cos90, sin90 }, // X ось повёрнута на 90° R2::Vector{ -sin90, cos90 } // Y ось повёрнута на 90° }; const R2::Point point{ 1., 0. }; const R2::Point result = transform.Apply( point ); // Результат = (5, 5) + (0, 1) * 1 + (-1, 0) * 0 = (5, 5) + (0, 1) = (5, 6) ASSERT_NEAR( 5., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 6., result.y(), R1::Distance::Epsilon() ); } // ============= Scaling Tests for MbPlacement Integration ============= TEST( R2Transform, ScalingUniform ) { // Равномерное масштабирование на 2.0 const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Point point{ 3., 4. }; const R2::Point result = scaling.Apply( point ); // Результат = (2, 0) * 3 + (0, 2) * 4 = (6, 8) ASSERT_NEAR( 6., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 8., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ScalingNonUniform ) { // Неравномерное масштабирование: X на 2.0, Y на 3.0 const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 3. } }; const R2::Point point{ 2., 2. }; const R2::Point result = scaling.Apply( point ); // Результат = (2, 0) * 2 + (0, 3) * 2 = (4, 6) ASSERT_NEAR( 4., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 6., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ScalingWithOriginShift ) { // Масштабирование относительно смещённого центра const R2::Transform scaling{ R2::Point{ 5., 5. }, // центр масштабирования R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Point point{ 1., 1. }; const R2::Point result = scaling.Apply( point ); // Результат = (5, 5) + (2, 0) * 1 + (0, 2) * 1 = (5, 5) + (2, 2) = (7, 7) ASSERT_NEAR( 7., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 7., result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ScalingPreservesOrigin ) { // Масштабирование переводит origin в себя const R2::Point origin{ 3., 4. }; const R2::Transform scaling{ origin, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Point result = scaling.Apply( R2::Point{} ); // Нулевая точка должна перейти в origin ASSERT_NEAR( origin.x(), result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( origin.y(), result.y(), R1::Distance::Epsilon() ); } TEST( R2Transform, ScalingScalesDistances ) { // Проверка, что масштабирование правильно масштабирует расстояния const R2::Transform scaling{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Point p1{ 1., 2. }; const R2::Point p2{ 4., 6. }; const R2::Point scaled_p1 = scaling.Apply( p1 ); const R2::Point scaled_p2 = scaling.Apply( p2 ); // Исходное расстояние const double dx = p2.x() - p1.x(); const double dy = p2.y() - p1.y(); const double original_dist = std::sqrt( dx * dx + dy * dy ); // Масштабированное расстояние const double scaled_dx = scaled_p2.x() - scaled_p1.x(); const double scaled_dy = scaled_p2.y() - scaled_p1.y(); const double scaled_dist = std::sqrt( scaled_dx * scaled_dx + scaled_dy * scaled_dy ); // Расстояние должно увеличиться в 2 раза ASSERT_NEAR( original_dist * 2., scaled_dist, 1e-10 ); } TEST( R2Transform, ScalingComposition ) { // Композиция двух масштабирований: 2x и 3x = 6x const R2::Transform scale2{ R2::Point{}, R2::Vector{ 2., 0. }, R2::Vector{ 0., 2. } }; const R2::Transform scale3{ R2::Point{}, R2::Vector{ 3., 0. }, R2::Vector{ 0., 3. } }; const R2::Transform composed = scale3 * scale2; const R2::Point point{ 1., 1. }; const R2::Point result = composed.Apply( point ); // Результат = (3, 0) * (2, 0) * 1 + (0, 3) * (0, 2) * 1 // = (3, 0) * (2, 0) + (0, 3) * (0, 2) // = (6, 0) + (0, 6) = (6, 6) ASSERT_NEAR( 6., result.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( 6., result.y(), R1::Distance::Epsilon() ); } // ============= C3D Bridge Scaling Tests ============= TEST( R2Transform, C3DBridgeScalingUniform ) { // ВНИМАНИЕ: Этот тест может провалиться из-за особенностей MbPlacement // MbPlacement может нормализовать или неправильно сохранять масштабированные вектора const R2::Vector ox_original{ 2., 0. }; const R2::Vector oy_original{ 0., 2. }; const R2::Transform original{ R2::Point{ 1., 1. }, ox_original, oy_original }; const auto c3d = c3d_bridge::MakeC3dPlacement( original ); const auto restored = c3d_bridge::GetTransform( c3d ); // Проверяем, сохранил ли MbPlacement масштабирование // Ожидаемый результат: ox=(2,0), oy=(0,2) const double ox_len = std::sqrt( restored.m_ox.x() * restored.m_ox.x() + restored.m_ox.y() * restored.m_ox.y() ); const double oy_len = std::sqrt( restored.m_oy.x() * restored.m_oy.x() + restored.m_oy.y() * restored.m_oy.y() ); // Задокументировать результат для анализа // std::cout << "Uniform Scaling (2x):" << std::endl; // std::cout << " Original: ox=(" << ox_original.x() << "," << ox_original.y() << ") " // << "oy=(" << oy_original.x() << "," << oy_original.y() << ")" << std::endl; // std::cout << " Restored: ox=(" << restored.m_ox.x() << "," << restored.m_ox.y() << ") " // << "oy=(" << restored.m_oy.x() << "," << restored.m_oy.y() << ")" << std::endl; // std::cout << " Lengths: ox_len=" << ox_len << ", oy_len=" << oy_len << std::endl; // ПРОВЕРКА 1: Origin должен быть сохранён ASSERT_NEAR( original.m_origin.x(), restored.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), restored.m_origin.y(), R1::Distance::Epsilon() ); // ПРОВЕРКА 2: Длины базисных векторов // MbPlacement нормализует вектора, длины будут 1.0 ASSERT_NEAR( 1., ox_len, 1e-10 ) << "X basis vector length should be normalized to 1.0"; ASSERT_NEAR( 1., oy_len, 1e-10 ) << "Y basis vector length should be normalized to 1.0"; // ПРОВЕРКА 3: После нормализации векторов проверяем направления const double normalized_ox_x = restored.m_ox.x() / ox_len; const double normalized_ox_y = restored.m_ox.y() / ox_len; const double normalized_oy_x = restored.m_oy.x() / oy_len; const double normalized_oy_y = restored.m_oy.y() / oy_len; const double expected_ox_x = ox_original.x() / 2.; const double expected_ox_y = ox_original.y() / 2.; const double expected_oy_x = oy_original.x() / 2.; const double expected_oy_y = oy_original.y() / 2.; ASSERT_NEAR( expected_ox_x, normalized_ox_x, 1e-10 ) << "Normalized X basis vector X component mismatch"; ASSERT_NEAR( expected_ox_y, normalized_ox_y, 1e-10 ) << "Normalized X basis vector Y component mismatch"; ASSERT_NEAR( expected_oy_x, normalized_oy_x, 1e-10 ) << "Normalized Y basis vector X component mismatch"; ASSERT_NEAR( expected_oy_y, normalized_oy_y, 1e-10 ) << "Normalized Y basis vector Y component mismatch"; } TEST( R2Transform, C3DBridgeScalingNonUniform ) { // ВНИМАНИЕ: Этот тест может провалиться из-за особенностей MbPlacement // Неравномерное масштабирование ещё более критично const R2::Vector ox_original{ 2., 0. }; const R2::Vector oy_original{ 0., 3. }; const R2::Transform original{ R2::Point{ 1., 1. }, ox_original, oy_original }; const auto c3d = c3d_bridge::MakeC3dPlacement( original ); const auto restored = c3d_bridge::GetTransform( c3d ); const double ox_len = std::sqrt( restored.m_ox.x() * restored.m_ox.x() + restored.m_ox.y() * restored.m_ox.y() ); const double oy_len = std::sqrt( restored.m_oy.x() * restored.m_oy.x() + restored.m_oy.y() * restored.m_oy.y() ); // std::cout << "Non-uniform Scaling (2x, 3x):" << std::endl; // std::cout << " Original: ox=(" << ox_original.x() << "," << ox_original.y() << ") " // << "oy=(" << oy_original.x() << "," << oy_original.y() << ")" << std::endl; // std::cout << " Restored: ox=(" << restored.m_ox.x() << "," << restored.m_ox.y() << ") " // << "oy=(" << restored.m_oy.x() << "," << restored.m_oy.y() << ")" << std::endl; // std::cout << " Lengths: ox_len=" << ox_len << ", oy_len=" << oy_len << std::endl; ASSERT_NEAR( original.m_origin.x(), restored.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), restored.m_origin.y(), R1::Distance::Epsilon() ); // Проверка нормализации векторов MbPlacement ASSERT_NEAR( 1., ox_len, 1e-10 ) << "X basis vector length should be normalized to 1.0"; ASSERT_NEAR( 1., oy_len, 1e-10 ) << "Y basis vector length should be normalized to 1.0"; // После нормализации векторов проверяем направления const double normalized_ox_x = restored.m_ox.x() / ox_len; const double normalized_ox_y = restored.m_ox.y() / ox_len; const double normalized_oy_x = restored.m_oy.x() / oy_len; const double normalized_oy_y = restored.m_oy.y() / oy_len; const double expected_ox_x = ox_original.x() / 2.; const double expected_ox_y = ox_original.y() / 2.; const double expected_oy_x = oy_original.x() / 3.; const double expected_oy_y = oy_original.y() / 3.; ASSERT_NEAR( expected_ox_x, normalized_ox_x, 1e-10 ) << "Normalized X basis vector X component mismatch"; ASSERT_NEAR( expected_ox_y, normalized_ox_y, 1e-10 ) << "Normalized X basis vector Y component mismatch"; ASSERT_NEAR( expected_oy_x, normalized_oy_x, 1e-10 ) << "Normalized Y basis vector X component mismatch"; ASSERT_NEAR( expected_oy_y, normalized_oy_y, 1e-10 ) << "Normalized Y basis vector Y component mismatch"; } TEST( R2Transform, C3DBridgeScalingWithRotation ) { // ВНИМАНИЕ: Сложный случай - масштабирование + поворот // MbPlacement может вообще не справиться с этим const double cos45 = std::sqrt( 2. ) / 2.; const double sin45 = std::sqrt( 2. ) / 2.; const R2::Vector ox_original{ 2. * cos45, 2. * sin45 }; const R2::Vector oy_original{ -2. * sin45, 2. * cos45 }; const R2::Transform original{ R2::Point{ 5., 5. }, ox_original, oy_original }; const auto c3d = c3d_bridge::MakeC3dPlacement( original ); const auto restored = c3d_bridge::GetTransform( c3d ); const double ox_len = std::sqrt( restored.m_ox.x() * restored.m_ox.x() + restored.m_ox.y() * restored.m_ox.y() ); const double oy_len = std::sqrt( restored.m_oy.x() * restored.m_oy.x() + restored.m_oy.y() * restored.m_oy.y() ); std::cout << "Scaling + Rotation (2x, 45°):" << std::endl; std::cout << " Original: ox=(" << ox_original.x() << "," << ox_original.y() << ") " << "oy=(" << oy_original.x() << "," << oy_original.y() << ")" << std::endl; std::cout << " Restored: ox=(" << restored.m_ox.x() << "," << restored.m_ox.y() << ") " << "oy=(" << restored.m_oy.x() << "," << restored.m_oy.y() << ")" << std::endl; std::cout << " Lengths: ox_len=" << ox_len << ", oy_len=" << oy_len << std::endl; ASSERT_NEAR( original.m_origin.x(), restored.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), restored.m_origin.y(), R1::Distance::Epsilon() ); // Проверка нормализации векторов с поворотом ASSERT_NEAR( 1., ox_len, 1e-10 ) << "Rotated X basis vector length should be normalized to 1.0"; ASSERT_NEAR( 1., oy_len, 1e-10 ) << "Rotated Y basis vector length should be normalized to 1.0"; // После нормализации векторов проверяем направления const double normalized_ox_x = restored.m_ox.x() / ox_len; const double normalized_ox_y = restored.m_ox.y() / ox_len; const double normalized_oy_x = restored.m_oy.x() / oy_len; const double normalized_oy_y = restored.m_oy.y() / oy_len; const double expected_ox_x = ox_original.x() / 2.; const double expected_ox_y = ox_original.y() / 2.; const double expected_oy_x = oy_original.x() / 2.; const double expected_oy_y = oy_original.y() / 2.; ASSERT_NEAR( expected_ox_x, normalized_ox_x, 1e-10 ) << "Normalized rotated X basis vector X component mismatch"; ASSERT_NEAR( expected_ox_y, normalized_ox_y, 1e-10 ) << "Normalized rotated X basis vector Y component mismatch"; ASSERT_NEAR( expected_oy_x, normalized_oy_x, 1e-10 ) << "Normalized rotated Y basis vector X component mismatch"; ASSERT_NEAR( expected_oy_y, normalized_oy_y, 1e-10 ) << "Normalized rotated Y basis vector Y component mismatch"; } // ============= C3D Bridge Tests ============= TEST( R2Transform, C3DBridgeIdentity ) { const R2::Transform original; const auto c3d = c3d_bridge::MakeC3dPlacement( original ); const auto restored = c3d_bridge::GetTransform( c3d ); ASSERT_NEAR( original.m_origin.x(), restored.m_origin.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_origin.y(), restored.m_origin.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.x(), restored.m_ox.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_ox.y(), restored.m_ox.y(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.x(), restored.m_oy.x(), R1::Distance::Epsilon() ); ASSERT_NEAR( original.m_oy.y(), restored.m_oy.y(), R1::Distance::Epsilon() ); }