diff --git a/src/Magnum/Math/Intersection.h b/src/Magnum/Math/Intersection.h index 0a3fd0166..77f4afa94 100644 --- a/src/Magnum/Math/Intersection.h +++ b/src/Magnum/Math/Intersection.h @@ -621,7 +621,7 @@ template bool sphereCone( const Vector3 diff = sphereCenter - coneOrigin; /* Point - cone test */ - if(Math::dot(diff - sphereRadius*sinAngle*coneNormal, coneNormal) > T(0)) { + if(Math::dot(diff + sphereRadius*sinAngle*coneNormal, coneNormal) > T(0)) { const Vector3 c = sinAngle*diff + coneNormal*sphereRadius; const T lenA = Math::dot(c, coneNormal); diff --git a/src/Magnum/Math/Test/IntersectionTest.cpp b/src/Magnum/Math/Test/IntersectionTest.cpp index 6b83a0c30..fb4eecced 100644 --- a/src/Magnum/Math/Test/IntersectionTest.cpp +++ b/src/Magnum/Math/Test/IntersectionTest.cpp @@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include @@ -53,6 +54,7 @@ struct IntersectionTest: TestSuite::Tester { void pointCone(); void pointDoubleCone(); void sphereCone(); + template void sphereConeNearApex(); void sphereConeView(); void sphereConeViewNotRigid(); void rangeCone(); @@ -68,6 +70,25 @@ using Magnum::Constants; using Magnum::Range3D; using Magnum::Rad; +constexpr struct { + const char* name; + Float angle; + Vector3 center; + Float radius; + bool intersects; +} SphereConeNearApexData[]{ + {"wide cone, beside apex", 120.0f, {1.5f, 0.0f, 0.0f}, 1.0f, true}, + {"wide cone, behind apex", 120.0f, {1.1f, 0.0f, -0.2f}, 1.0f, true}, + {"wide cone, in front of apex", 120.0f, {1.5f, 0.0f, 0.2f}, 1.0f, true}, + {"narrow cone, beside apex", 60.0f, {1.1f, 0.0f, 0.0f}, 1.0f, true}, + {"outside side", 120.0f, {2.5f, 0.0f, 0.0f}, 1.0f, false}, + {"outside behind apex", 120.0f, {0.0f, 0.0f, -1.5f}, 1.0f, false}, + {"contains apex", 120.0f, {0.0f, 0.0f, -0.5f}, 1.0f, true}, + {"touches apex", 120.0f, {0.0f, 0.0f, -1.0f}, 1.0f, true}, + {"inside cone", 120.0f, {0.0f, 0.0f, 3.0f}, 1.0f, true}, + {"zero radius outside", 120.0f, {1.0f, 0.0f, 0.0f}, 0.0f, false} +}; + IntersectionTest::IntersectionTest() { addTests({&IntersectionTest::pointCircle, &IntersectionTest::pointSphere, @@ -88,6 +109,10 @@ IntersectionTest::IntersectionTest() { &IntersectionTest::sphereConeViewNotRigid, &IntersectionTest::rangeCone, &IntersectionTest::aabbCone}); + + addInstancedTests({&IntersectionTest::sphereConeNearApex, + &IntersectionTest::sphereConeNearApex}, + Containers::arraySize(SphereConeNearApexData)); } void IntersectionTest::pointCircle() { @@ -407,6 +432,44 @@ void IntersectionTest::sphereCone() { CORRADE_VERIFY(!Intersection::sphereCone(center - 4.0f*surface, 0.5f, center, normal, angle)); } +template void IntersectionTest::sphereConeNearApex() { + auto&& data = SphereConeNearApexData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(TypeTraits::name()); + + const Math::Vector3 sphereCenter{data.center}; + const T radius = T(data.radius); + const Math::Rad angle{Math::Deg{T(data.angle)}}; + const T sinAngle = Math::sin(angle/T(2)); + const T tanAngle = Math::tan(angle/T(2)); + const Math::Vector3 normal = Math::Vector3::zAxis(); + + /* A sphere beside the apex can intersect the cone's side without + containing the apex. For example, at (1.5, 0, 0) the distance to the + side of a 120-degree cone is 1.5*cos(60 degrees) = 0.75. */ + CORRADE_COMPARE(Intersection::sphereCone(sphereCenter, radius, + Math::Vector3{}, normal, angle), data.intersects); + CORRADE_COMPARE(Intersection::sphereCone(sphereCenter, radius, + Math::Vector3{}, normal, sinAngle, T(1) + tanAngle*tanAngle), data.intersects); + + /* The view-based overload should agree, also after rotating and + translating the cone and the sphere together. */ + const Math::Vector3 origin{T(1), T(-2), T(3)}; + const Math::Vector3 rotatedNormal = Math::Vector3::yAxis(); + const Math::Vector3 transformedCenter = origin + + Math::Vector3{sphereCenter.x(), sphereCenter.z(), -sphereCenter.y()}; + const Math::Matrix4 view = Math::Matrix4::lookAt(origin, + origin + rotatedNormal, Math::Vector3::zAxis()).invertedRigid(); + CORRADE_COMPARE(Intersection::sphereCone(transformedCenter, radius, + origin, rotatedNormal, angle), data.intersects); + CORRADE_COMPARE(Intersection::sphereCone(transformedCenter, radius, + origin, rotatedNormal, sinAngle, T(1) + tanAngle*tanAngle), data.intersects); + CORRADE_COMPARE(Intersection::sphereConeView(transformedCenter, radius, + view, angle), data.intersects); + CORRADE_COMPARE(Intersection::sphereConeView(transformedCenter, radius, + view, sinAngle, tanAngle), data.intersects); +} + void IntersectionTest::sphereConeView() { const Vector3 center{1.0f, -2.0f, 1.3f}; const Vector3 normal{Vector3{0.5f, 1.0f, 2.0f}.normalized()};