Skip to content

Implement axis circle intersection in 2d #157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions src/Axis2d.elm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Axis2d exposing
, x, y
, through, withDirection, throughPoints
, originPoint, direction
, intersectionPoint
, intersectionPoint, intersectionWithCircle
, reverse, moveTo, rotateAround, rotateBy, translateBy, translateIn, mirrorAcross
, at, at_
, relativeTo, placeIn
Expand Down Expand Up @@ -45,7 +45,7 @@ by an origin point and direction. Axes have several uses, such as:

# Intersection

@docs intersectionPoint
@docs intersectionPoint, intersectionWithCircle


# Transformations
Expand All @@ -66,9 +66,9 @@ by an origin point and direction. Axes have several uses, such as:

import Angle exposing (Angle)
import Direction2d exposing (Direction2d)
import Geometry.Types as Types exposing (Frame2d)
import Geometry.Types as Types exposing (Circle2d, Frame2d)
import Point2d exposing (Point2d)
import Quantity exposing (Quantity, Rate)
import Quantity exposing (Quantity(..), Rate)
import Vector2d exposing (Vector2d)


Expand Down Expand Up @@ -231,6 +231,48 @@ intersectionPoint axis1 axis2 =
Just (Point2d.along axis2 (distance2 |> Quantity.divideBy -crossProduct))


{-| Attempt to find the intersection of an axis with a circle. The two points
will be in order of signed distance along the axis. Returns `Nothing` if there
is no intersection.
-}
intersectionWithCircle : Circle2d units coordinates -> Axis2d units coordinates -> Maybe ( Point2d units coordinates, Point2d units coordinates )
intersectionWithCircle (Types.Circle2d { centerPoint, radius }) (Types.Axis2d axis) =
let
circleCenterToOrigin =
Vector2d.from centerPoint axis.originPoint

(Types.Vector2d cto) =
circleCenterToOrigin

ctoLengthSquared =
cto.x ^ 2 + cto.y ^ 2

(Quantity dotProduct) =
Vector2d.componentIn axis.direction circleCenterToOrigin

(Quantity r) =
radius

inRoot =
dotProduct ^ 2 - ctoLengthSquared + r ^ 2
in
if inRoot < 0 then
Nothing

else
let
d1 =
-dotProduct - sqrt inRoot

d2 =
-dotProduct + sqrt inRoot
in
Just
( Point2d.along axis (Quantity d1)
, Point2d.along axis (Quantity d2)
)


{-| Reverse the direction of an axis while keeping the same origin point.
-}
reverse : Axis2d units coordinates -> Axis2d units coordinates
Expand Down
77 changes: 77 additions & 0 deletions tests/Tests/Axis2d.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Tests.Axis2d exposing
( directionExample
, intersectionPoint
, intersectionWithCircle
, mirrorAcrossExample
, moveToExample
, originPointExample
Expand All @@ -16,6 +17,7 @@ module Tests.Axis2d exposing

import Angle
import Axis2d
import Circle2d
import Direction2d
import Expect
import Frame2d
Expand Down Expand Up @@ -214,3 +216,78 @@ intersectionPoint =
-- numerically badly behaved)
Expect.pass
)


intersectionWithCircle : Test
intersectionWithCircle =
Test.describe "intersectionWithCircle"
[ Test.test "no intersection points" <|
\_ ->
let
sphere =
Circle2d.withRadius (meters 1) (Point2d.meters 0 0)

axis =
Axis2d.through (Point2d.meters 6 0) Direction2d.y
in
Expect.equal (Axis2d.intersectionWithCircle sphere axis) Nothing
, Test.test "two intersection points" <|
\_ ->
let
sphere =
Circle2d.withRadius (meters 1) (Point2d.meters 0 0)

axis =
Axis2d.through (Point2d.meters 0 0) Direction2d.y
in
Expect.equal (Axis2d.intersectionWithCircle sphere axis)
(Just ( Point2d.meters 0 -1, Point2d.meters 0 1 ))
, Test.test "the same intersection points" <|
\_ ->
let
sphere =
Circle2d.withRadius (meters 1) (Point2d.meters 0 0)

axis =
Axis2d.through (Point2d.meters 1 0) Direction2d.y
in
Expect.equal (Axis2d.intersectionWithCircle sphere axis)
(Just ( Point2d.meters 1 0, Point2d.meters 1 0 ))
, Test.check2 "intersection points should be on the circle and the axis"
Random.axis2d
Random.circle2d
(\axis circle ->
case Axis2d.intersectionWithCircle circle axis of
Just pointPair ->
let
-- An intersection point should be on the circle
-- (have a distance from the circle center point
-- equal to the circle radius), and on the axis
-- (have a zero distance from the axis)
validIntersectionPoint point =
Expect.all
[ Point2d.distanceFrom (Circle2d.centerPoint circle)
>> Expect.quantity (Circle2d.radius circle)
, Point2d.signedDistanceFrom axis
>> Expect.quantity Quantity.zero
]
point
in
-- Both intersection points should be valid
Expect.all
[ Tuple.first >> validIntersectionPoint
, Tuple.second >> validIntersectionPoint
]
pointPair

Nothing ->
-- If the axis does not intersect the circle, then the
-- absolute distance from the circle center point to the axis
-- should be greater than the radius of the circle (if
-- it was less, then there should be an intersection!)
Circle2d.centerPoint circle
|> Point2d.signedDistanceFrom axis
|> Quantity.abs
|> Expect.quantityGreaterThan (Circle2d.radius circle)
)
]