//============================================================================ // Copyright (c) Kitware, Inc. // All rights reserved. // See LICENSE.txt for details. // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ #include #include #include #include #include #include #include //----------------------------------------------------------------------------- namespace { class Coords { public: static constexpr vtkm::IdComponent NUM_COORDS = 5; template VTKM_EXEC_CONT vtkm::Vec EndpointList(vtkm::Int32 i) const { vtkm::Float64 coords[NUM_COORDS][3] = { { 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { -1.0, 0.0, 0.0 }, { -2.0, 0.0, 0.0 }, { 0.0, -2.0, 0.0 }, }; vtkm::Int32 j = i % NUM_COORDS; return vtkm::Vec( static_cast(coords[j][0]), static_cast(coords[j][1]), static_cast(coords[j][2])); } template VTKM_EXEC_CONT vtkm::Vec ClosestToOriginList(vtkm::Int32 i) const { vtkm::Float64 coords[NUM_COORDS][3] = { { 0.5, 0.5, 0.0 }, { -0.5, 0.5, 0.0 }, { -1.0, 0.0, 0.0 }, { -1.0, -1.0, 0.0 }, { 0.8, -0.4, 0.0 }, }; vtkm::Int32 j = i % NUM_COORDS; return vtkm::Vec( static_cast(coords[j][0]), static_cast(coords[j][1]), static_cast(coords[j][2])); } template VTKM_EXEC_CONT T DistanceToOriginList(vtkm::Int32 i) const { vtkm::Float64 coords[NUM_COORDS] = { 0.707107, 0.707107, 1.0, 1.41421, 0.894427, }; vtkm::Int32 j = i % NUM_COORDS; return static_cast(coords[j]); } }; //----------------------------------------------------------------------------- template struct RayTests : public vtkm::exec::FunctorBase { VTKM_EXEC void operator()(vtkm::Id) const { { using V2 = vtkm::Vec; using Ray2 = vtkm::Ray2; Ray2 ray0; VTKM_MATH_ASSERT(test_equal(ray0.Origin, V2(0., 0.)), "Bad origin for default 2D ray ctor."); VTKM_MATH_ASSERT(test_equal(ray0.Direction, V2(1., 0.)), "Bad direction for default 2D ray ctor."); // Test intersection Ray2 ray1(V2(-1., 0.), V2(+1., +1.)); Ray2 ray2(V2(+1., 0.), V2(-1., +1.)); V2 point; bool didIntersect = ray1.Intersect(ray2, point); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Ray-pair 1 should intersect."); VTKM_MATH_ASSERT(test_equal(point, V2(0., 1.)), "Ray-pair 1 should intersect at (0,1)."); // Test non intersection Ray2 ray3(V2(-1., 0.), V2(-1., -1.)); Ray2 ray4(V2(+1., 0.), V2(+1., -1.)); didIntersect = ray1.Intersect(ray4, point); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Ray-pair 2 should not intersect."); VTKM_MATH_ASSERT(test_equal(point, V2(0., 1.)), "Ray-pair 2 should intersect at (0,1)."); didIntersect = ray3.Intersect(ray2, point); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Ray-pair 3 should not intersect."); VTKM_MATH_ASSERT(test_equal(point, V2(0., 1.)), "Ray-pair 3 should intersect at (0,1)."); didIntersect = ray3.Intersect(ray4, point); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Ray-pair 4 should not intersect."); VTKM_MATH_ASSERT(test_equal(point, V2(0., 1.)), "Ray-pair 4 should intersect at (0,1)."); } { using V3 = vtkm::Vec; vtkm::Ray ray0; VTKM_MATH_ASSERT(test_equal(ray0.Origin, V3(0., 0., 0.)), "Bad origin for default 3D ray ctor."); VTKM_MATH_ASSERT(test_equal(ray0.Direction, V3(1., 0., 0.)), "Bad direction for default 3D ray ctor."); } } }; struct TryRayTests { template void operator()(const T&) const { vtkm::cont::Algorithm::Schedule(RayTests(), 1); } }; //----------------------------------------------------------------------------- template struct LineSegmentTests : public vtkm::exec::FunctorBase { VTKM_EXEC void operator()(vtkm::Id) const { { using V2 = vtkm::Vec; using Line2 = vtkm::Line2; vtkm::LineSegment seg0; VTKM_MATH_ASSERT(test_equal(seg0.Endpoints[0], V2(0., 0.)), "Bad origin for default 2D line segment ctor."); VTKM_MATH_ASSERT(test_equal(seg0.Endpoints[1], V2(1., 0.)), "Bad direction for default 2D line segment ctor."); V2 p0(1., 1.); V2 p1(3., 3.); V2 p2(2., 2.); // V2 p3(static_cast(1.2928932), static_cast(2.7071068)); V2 dir(static_cast(-0.7071068), static_cast(0.7071068)); vtkm::LineSegment seg1(p0, p1); Line2 ray = seg1.PerpendicularBisector(); VTKM_MATH_ASSERT(test_equal(ray.Origin, p2), "Perpendicular bisector origin failed in 2D."); VTKM_MATH_ASSERT(test_equal(ray.Direction, dir), "Perpendicular bisector direction failed in 2D."); } { using V3 = vtkm::Vec; vtkm::LineSegment seg0; VTKM_MATH_ASSERT(test_equal(seg0.Endpoints[0], V3(0., 0., 0.)), "Bad origin for default 3D line segment ctor."); VTKM_MATH_ASSERT(test_equal(seg0.Endpoints[1], V3(1., 0., 0.)), "Bad direction for default 3D line segment ctor."); V3 p0(1., 1., 0.); V3 p1(3., 3., 0.); V3 p2(2., 2., 0.); V3 p3(static_cast(0.70710678), static_cast(0.70710678), 0.); vtkm::LineSegment seg1(p0, p1); vtkm::Plane bisector = seg1.PerpendicularBisector(); VTKM_MATH_ASSERT(test_equal(bisector.Origin, p2), "Perpendicular bisector origin failed in 3D."); VTKM_MATH_ASSERT(test_equal(bisector.Normal, p3), "Perpendicular bisector direction failed in 3D."); } vtkm::Vec origin(0., 0., 0.); for (vtkm::IdComponent index = 0; index < Coords::NUM_COORDS; ++index) { auto p0 = Coords{}.EndpointList(index); auto p1 = Coords{}.EndpointList((index + 1) % Coords::NUM_COORDS); vtkm::LineSegment segment(p0, p1); vtkm::Vec closest; T param; auto dp0 = segment.DistanceTo(p0); auto dp1 = segment.DistanceTo(p1, param, closest); VTKM_MATH_ASSERT(test_equal(dp0, 0.0), "Distance to endpoint 0 not zero."); VTKM_MATH_ASSERT(test_equal(dp1, 0.0), "Distance to endpoint 1 not zero."); VTKM_MATH_ASSERT(test_equal(param, 1.0), "Parameter value of endpoint 1 not 1.0."); VTKM_MATH_ASSERT(test_equal(p1, closest), "Closest point not endpoint 1."); closest = segment.Evaluate(static_cast(0.0)); VTKM_MATH_ASSERT(test_equal(p0, closest), "Evaluated point not endpoint 0."); auto dpo = segment.DistanceTo(origin, param, closest); auto clo = Coords{}.ClosestToOriginList(index); auto dst = Coords{}.DistanceToOriginList(index); VTKM_MATH_ASSERT(test_equal(closest, clo), "Closest point to origin doesn't match."); VTKM_MATH_ASSERT(test_equal(dpo, dst), "Distance to origin doesn't match."); } } }; struct TryLineSegmentTests { template void operator()(const T&) const { vtkm::cont::Algorithm::Schedule(LineSegmentTests(), 1); } }; //----------------------------------------------------------------------------- template struct PlaneTests : public vtkm::exec::FunctorBase { VTKM_EXEC void operator()(vtkm::Id) const { vtkm::Vec origin(0., 0., 0.); vtkm::Vec zvectr(0., 0., 5.); // intentionally not unit length to test normalization. vtkm::Plane plane; vtkm::LineSegment segment; T dist; bool didIntersect; bool isLineInPlane; vtkm::Vec nearest; vtkm::Vec p0; vtkm::Vec p1; T param; // Test signed plane-point distance plane = vtkm::Plane(origin, zvectr); dist = plane.DistanceTo(vtkm::Vec(82., 0.5, 1.25)); VTKM_MATH_ASSERT(test_equal(dist, 1.25), "Bad positive point-plane distance."); dist = plane.DistanceTo(vtkm::Vec(82., 0.5, -1.25)); VTKM_MATH_ASSERT(test_equal(dist, -1.25), "Bad negative point-plane distance."); dist = plane.DistanceTo(vtkm::Vec(82., 0.5, 0.0)); VTKM_MATH_ASSERT(test_equal(dist, 0.0), "Bad zero point-plane distance."); // Test line intersection { // Case 1. No intersection segment = vtkm::LineSegment((p0 = vtkm::Vec(1., 1., 1.)), (p1 = vtkm::Vec(2., 2., 2.))); didIntersect = plane.Intersect(segment, param, nearest, isLineInPlane); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Plane and line should not intersect (1)."); VTKM_MATH_ASSERT(test_equal(isLineInPlane, false), "Line improperly reported as in plane (1)."); VTKM_MATH_ASSERT(test_equal(nearest, p0), "Unexpected nearest point (1)."); VTKM_MATH_ASSERT(test_equal(param, 0.0), "Unexpected nearest parameter value (1)."); // Case 2. Degenerate intersection (entire segment lies in plane) segment = vtkm::LineSegment((p0 = vtkm::Vec(1., 1., 0.)), (p1 = vtkm::Vec(2., 2., 0.))); didIntersect = plane.Intersect(segment, param, nearest, isLineInPlane); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Plane and line should intersect (2)."); VTKM_MATH_ASSERT(test_equal(isLineInPlane, true), "Line improperly reported as out of plane (2)."); // Case 3. Endpoint intersection segment = vtkm::LineSegment((p0 = vtkm::Vec(1., 1., 1.)), (p1 = vtkm::Vec(2., 2., 0.))); didIntersect = plane.Intersect(segment, param, nearest, isLineInPlane); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Plane and line should intersect (3a)."); VTKM_MATH_ASSERT(test_equal(isLineInPlane, false), "Line improperly reported as in plane (3a)."); VTKM_MATH_ASSERT(test_equal(param, 1.0), "Invalid parameter for intersection point (3a)."); VTKM_MATH_ASSERT(test_equal(nearest, p1), "Invalid intersection point (3a)."); segment = vtkm::LineSegment((p0 = vtkm::Vec(1., 1., 0.)), (p1 = vtkm::Vec(2., 2., 1.))); didIntersect = plane.Intersect(segment, param, nearest, isLineInPlane); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Plane and line should intersect (3b)."); VTKM_MATH_ASSERT(test_equal(isLineInPlane, false), "Line improperly reported as in plane (3b)."); VTKM_MATH_ASSERT(test_equal(param, 0.0), "Invalid parameter for intersection point (3b)."); VTKM_MATH_ASSERT(test_equal(nearest, p0), "Invalid intersection point (3b)."); // Case 4. General-position intersection segment = vtkm::LineSegment((p0 = vtkm::Vec(-1., -1., -1.)), (p1 = vtkm::Vec(2., 2., 1.))); didIntersect = plane.Intersect(segment, param, nearest, isLineInPlane); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Plane and line should intersect (4)."); VTKM_MATH_ASSERT(test_equal(isLineInPlane, false), "Line improperly reported as in plane (4)."); VTKM_MATH_ASSERT(test_equal(param, 0.5), "Invalid parameter for intersection point (4)."); VTKM_MATH_ASSERT(test_equal(nearest, vtkm::Vec(0.5, 0.5, 0)), "Invalid intersection point (4)."); } // Test plane-plane intersection { using V3 = vtkm::Vec; using PlaneType = vtkm::Plane; // Case 1. Coincident planes p0 = V3(1., 2., 3.); p1 = V3(5., 7., -6.); V3 nn = vtkm::Normal(V3(1., 1., 1)); PlaneType pa(p0, nn); PlaneType pb(p1, nn); vtkm::Line3 ii; bool coincident; didIntersect = pa.Intersect(pb, ii, coincident); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Coincident planes should have degenerate intersection."); VTKM_MATH_ASSERT(test_equal(coincident, true), "Coincident planes should be marked coincident."); // Case 2. Offset planes p1 = V3(5., 6., 7.); pb = PlaneType(p1, nn); didIntersect = pa.Intersect(pb, ii, coincident); VTKM_MATH_ASSERT(test_equal(didIntersect, false), "Offset planes should have degenerate intersection."); VTKM_MATH_ASSERT(test_equal(coincident, false), "Offset planes should not be marked coincident."); // Case 3. General position p1 = V3(1., 2., 0.); V3 n2(0., 0., 1.); pb = PlaneType(p1, n2); didIntersect = pa.Intersect(pb, ii, coincident); VTKM_MATH_ASSERT(test_equal(didIntersect, true), "Proper planes should have non-degenerate intersection."); VTKM_MATH_ASSERT(test_equal(coincident, false), "Proper planes should not be marked coincident."); VTKM_MATH_ASSERT(test_equal(ii.Origin, V3(2.5, 3.5, 0)), "Unexpected intersection-line base point."); VTKM_MATH_ASSERT(test_equal(ii.Direction, vtkm::Normal(V3(1, -1, 0))), "Unexpected intersection-line direction."); } } }; struct TryPlaneTests { template void operator()(const T&) const { vtkm::cont::Algorithm::Schedule(PlaneTests(), 1); } }; //----------------------------------------------------------------------------- template struct SphereTests : public vtkm::exec::FunctorBase { VTKM_EXEC void operator()(vtkm::Id) const { { using V2 = vtkm::Vec; V2 origin(0., 0.); vtkm::Sphere defaultSphere; VTKM_MATH_ASSERT(test_equal(defaultSphere.Center, origin), "Default circle not at origin."); VTKM_MATH_ASSERT(test_equal(defaultSphere.Radius, 1.0), "Default circle not unit radius."); vtkm::Sphere sphere(origin, -2.); VTKM_MATH_ASSERT(test_equal(sphere.Radius, -1.0), "Negative radius should be reset to -1."); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), false), "Negative radius should leave sphere invalid."); sphere = vtkm::Circle(origin, 1.0); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Circle assignment failed."); VTKM_MATH_ASSERT(test_equal(sphere.Contains(origin), true), "Circle does not contain its center."); VTKM_MATH_ASSERT(test_equal(sphere.Classify(V2(1., 0.)), 0), "Circle point not on boundary."); VTKM_MATH_ASSERT(test_equal(sphere.Classify(V2(0.75, 0.75)), +1), "Circle contains a point that should be outside."); V2 p0(static_cast(-0.7071), static_cast(-0.7071)); V2 p1(static_cast(+0.7071), static_cast(-0.7071)); V2 p2(static_cast(0.0), static_cast(1.0)); sphere = make_CircleFrom3Points(p0, p1, p2); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Could not create 3-point circle."); V2 p3(1, 1); V2 p4(3, 4); V2 p5(5, 12); sphere = make_CircleFrom3Points(p3, p4, p5); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Could not create 3-point circle."); T tol = static_cast(1e-3); // Use a loose tolerance VTKM_MATH_ASSERT(test_equal(sphere.Center, vtkm::Vec(-12.4f, 12.1f)), "Invalid circle center."); VTKM_MATH_ASSERT(test_equal(sphere.Radius, static_cast(17.400291f)), "Invalid circle radius."); VTKM_MATH_ASSERT(test_equal(sphere.Classify(p3, tol), 0), "Generator p3 not on circle boundary."); VTKM_MATH_ASSERT(test_equal(sphere.Classify(p4, tol), 0), "Generator p4 not on circle boundary."); VTKM_MATH_ASSERT(test_equal(sphere.Classify(p5, tol), 0), "Generator p5 not on circle boundary."); V2 p6(1, 1); V2 p7(4, 4); V2 p8(5, 5); sphere = make_CircleFrom3Points(p6, p7, p8); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), false), "3-point circle construction should fail with points on a line."); } { using V3 = vtkm::Vec; V3 p0(0., 1., 0.); V3 p1(1., 0., 0.); V3 p2(-1., 0., 0.); V3 p3(0., 0., 1.); V3 p4 = vtkm::Normal(V3(1., 1., 1.)); V3 origin(0., 0., 0.); vtkm::Sphere defaultSphere; VTKM_MATH_ASSERT(test_equal(defaultSphere.Center, origin), "Default sphere not at origin."); VTKM_MATH_ASSERT(test_equal(defaultSphere.Radius, 1.0), "Default sphere not unit radius."); vtkm::Sphere sphere = make_SphereFrom4Points(p0, p1, p2, p3, static_cast(1.0e-6)); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Easy sphere 1 not valid."); VTKM_MATH_ASSERT(test_equal(sphere.Center, origin), "Easy sphere 1 not at origin."); VTKM_MATH_ASSERT(test_equal(sphere.Radius, 1.0), "Easy sphere 1 not unit radius."); sphere = make_SphereFrom4Points(p0, p1, p2, p4, static_cast(1.0e-6)); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Easy sphere 2 not valid."); VTKM_MATH_ASSERT(test_equal(sphere.Center, origin), "Easy sphere 2 not at origin."); VTKM_MATH_ASSERT(test_equal(sphere.Radius, 1.0), "Easy sphere 2 not unit radius."); V3 fancyCenter(1, 2, 3); T fancyRadius(2.5); V3 fp0 = fancyCenter + fancyRadius * p0; V3 fp1 = fancyCenter + fancyRadius * p1; V3 fp2 = fancyCenter + fancyRadius * p2; V3 fp4 = fancyCenter + fancyRadius * p4; sphere = make_SphereFrom4Points(fp0, fp1, fp2, fp4, static_cast(1.0e-6)); VTKM_MATH_ASSERT(test_equal(sphere.IsValid(), true), "Medium sphere 1 not valid."); VTKM_MATH_ASSERT(test_equal(sphere.Center, fancyCenter), "Medium sphere 1 not at (1,2,3)."); VTKM_MATH_ASSERT(test_equal(sphere.Radius, fancyRadius), "Medium sphere 1 not radius 2.5."); } } }; struct TrySphereTests { template void operator()(const T&) const { vtkm::cont::Algorithm::Schedule(SphereTests(), 1); } }; //----------------------------------------------------------------------------- void RunGeometryTests() { std::cout << "Tests for rays." << std::endl; vtkm::testing::Testing::TryTypes(TryRayTests(), vtkm::TypeListFieldScalar()); std::cout << "Tests for line segments." << std::endl; vtkm::testing::Testing::TryTypes(TryLineSegmentTests(), vtkm::TypeListFieldScalar()); std::cout << "Tests for planes." << std::endl; vtkm::testing::Testing::TryTypes(TryPlaneTests(), vtkm::TypeListFieldScalar()); std::cout << "Tests for spheres." << std::endl; vtkm::testing::Testing::TryTypes(TrySphereTests(), vtkm::TypeListFieldScalar()); } } // anonymous namespace int UnitTestGeometry(int argc, char* argv[]) { return vtkm::cont::testing::Testing::Run(RunGeometryTests, argc, argv); }