brush-strokes-0.1.0.0: src/lib/Math/Orientation.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Math.Orientation
( Orientation(..), reverseOrientation
, convexOrientation, splineOrientation, splineTangents
, between
)
where
-- base
import Control.Monad
( guard )
import Data.Fixed
( mod' )
-- acts
import Data.Act
( Torsor((-->)) )
-- containers
import Data.Sequence
( Seq(..) )
-- generic-lens
import Data.Generics.Product.Typed
( HasType(typed) )
import Data.Generics.Internal.VL
( view )
-- brush-strokes
import Math.Epsilon
( nearZero )
import Math.Module
( (×) )
import Math.Bezier.Spline
( Spline(..), Curves(..), Curve(..), NextPoint(..)
, SplineType(..), KnownSplineType(..), SSplineType(..)
, ssplineType
)
import Math.Linear
( ℝ(..), T(..) )
--------------------------------------------------------------------------------
-- | An orientation in the plane: counter-clockwise or clockwise.
data Orientation = CCW | CW
deriving stock ( Show, Eq, Ord )
-- | Reverse an orientation, turning counter-clockwise into clockwise and vice-versa.
reverseOrientation :: Orientation -> Orientation
reverseOrientation CCW = CW
reverseOrientation CW = CCW
-- | Compute an orientation from a sequence of tangent vectors (assumed to have monotone angle).
convexOrientation :: [ T ( ℝ 2 ) ] -> Orientation
convexOrientation ( v1 : v2 : vs )
| nearZero crossProduct
= convexOrientation ( v2 : vs )
| crossProduct < 0
= CW
| otherwise
= CCW
where
crossProduct :: Double
crossProduct = v1 × v2
convexOrientation _ = CCW -- default
-- | Compute the orientation of a spline, assuming tangent vectors have a monotone angle.
splineOrientation
:: forall clo crvData ptData
. ( KnownSplineType clo, HasType ( ℝ 2 ) ptData )
=> Spline clo crvData ptData
-> Orientation
splineOrientation = convexOrientation . splineTangents
-- | Compute the sequence of tangent vectors given by the control points of a Bézier spline.
splineTangents
:: forall clo crvData ptData
. ( KnownSplineType clo, HasType ( ℝ 2 ) ptData )
=> Spline clo crvData ptData
-> [ T ( ℝ 2 ) ]
splineTangents spline@( Spline { splineStart = sp0, splineCurves = curves } )
| let
p0 :: ℝ 2
p0 = view typed sp0
= case ssplineType @clo of
SOpen
| OpenCurves { openCurves = cs } <- curves
-> go p0 cs
SClosed
| OpenCurves cs@( c :<| _ ) <- splineCurves $ adjustSplineType @Open spline
-> go p0 ( cs :|> c )
_ -> []
where
go :: ℝ 2 -> Seq ( Curve Open crvData ptData ) -> [ T ( ℝ 2 ) ]
go _ Empty = []
go p ( crv :<| crvs ) =
case crv of
LineTo { curveEnd = NextPoint sq }
| let
q :: ℝ 2
q = view typed sq
-> ( p --> q ) : go q crvs
Bezier2To { controlPoint = scp, curveEnd = NextPoint sq }
| let
cp, q :: ℝ 2
cp = view typed scp
q = view typed sq
-> ( p --> cp ) : ( cp --> q ) : go q crvs
Bezier3To { controlPoint1 = scp1, controlPoint2 = scp2, curveEnd = NextPoint sq }
| let
cp1, cp2, q :: ℝ 2
cp1 = view typed scp1
cp2 = view typed scp2
q = view typed sq
-> ( p --> cp1 ) : ( cp1 --> cp2 ) : ( cp2 --> q ) : go q crvs
-- | Checks whether a 2D vector lies "in between" two other vectors according to a given orientation,
-- i.e. whether the angle of the query vector lies in between the angles of the start and end vectors.
--
-- The input vectors are expected to be within π radians of each other (as we are dealing
-- with convex shapes).
--
-- Returns the proportion of the angle the vector is in between, or @Nothing@ if the query vector
-- is not in between.
--
-- >>> between CCW ( V2 1 0 ) ( V2 -1 1 ) ( V2 1 1 )
-- Just 0.3333333333333333
between
:: Orientation
-> T ( ℝ 2 ) -- ^ start vector
-> T ( ℝ 2 ) -- ^ end vector
-> T ( ℝ 2 ) -- ^ query vector: is it in between the start and end vectors w.r.t. the provided orientation?
-> Maybe Double
between CCW ( V2 x1 y1 ) ( V2 x2 y2 ) ( V2 a b ) =
let
τ, η, φ, θ :: Double
τ = 2 * pi
η = atan2 y1 x1
φ = ( atan2 y2 x2 - η ) `mod'` τ
θ = ( atan2 b a - η ) `mod'` τ
in do
guard ( φ < pi ) -- deals with possible floating-point inaccuracies,
-- which can cause the angles of the two reference vectors
-- to be flipped, e.g. arg(v2) = arg(v1) - 0.000000001
guard ( θ <= φ )
pure ( θ / φ )
between CW v1 v2 u = ( 1 - ) <$> between CCW v2 v1 u