moonlight-planar-1.2.0.0: src-dcel/Moonlight/Planar/Internal/CurveSource.hs
-- | Source-span provenance and selection for canonical curves: the one owner
-- of which step of a source a span refines and where a site on it lies.
--
-- It owns three invariants. A 'SourceStep' is built only by 'sourceSteps',
-- from the source's own 'trailSteps', or 'closedTrailSteps' for a closed
-- trail, so its index and count are valid for its source by construction and
-- its located start is the anchor translated by the steps before it. A closed
-- trail's seam is a join: the closing step's end and the first step's start
-- are one point. A 'TrailSite' is built only by 'trailSite', which decides its
-- 'JoinSide' from the step's index and count and the source's closure.
--
-- A site's point is its step's exact point at its parameter: the step's
-- located start translated by 'evaluateStep'. 'trailSite' takes the point as
-- given, so that obligation rests with its callers inside the package:
-- 'selectSite' evaluates the step; measurement and proximity carry the point
-- of their exact subdivision, which agrees with evaluation; and proximity's
-- nearest point on a straight piece is the step's own affine point at the
-- parameter.
module Moonlight.Planar.Internal.CurveSource
( SourceStep
, sourceSteps
, sourceStepIndex
, sourceStepCount
, sourceStepStart
, sourceStepCurve
, JoinSide (..)
, TrailSite
, trailSite
, siteSource
, siteStep
, siteStepIndex
, siteParameter
, sitePoint
, siteJoinSide
, siteJet
, selectSite
, joinNeighbourJet
, SourceSpan
, wholeSpan
, halveSpan
, sourceSpanStep
, sourceSpanFrom
, sourceSpanTo
, sourceSpanStart
, sourceSpanPiece
, sourceSpanControls
, admitSpan
, spanBits
) where
import Control.DeepSeq (NFData (..))
import Data.List.NonEmpty (NonEmpty)
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Moonlight.Planar.Curve
( CurveShapeView (..), CurveStep, StepJet, Subpath (..), closedTrailSteps, curveStepEnd
, curveStepShape, evaluateStep, jetStep, location, locatedValue, shapeView, splitStep
, stepControlPoints, trailSteps )
import Moonlight.Planar.Exact
( ExactPoint, ExactVector (..), UnitInterval, exactPointBitWidth, exactRationalBitWidth
, positiveExactValue, translateExactPoint, unitHalf, unitIntervalValue, unitOne, unitZero )
import Moonlight.Planar.Internal.CurveBudget (BudgetObligation (..), SubdivisionBudget, budgetBits)
import Moonlight.Planar.Internal.ExactRational (unitMidpoint)
-- | One step of a source as the source itself has it: its index among the
-- source's actual steps, their count, its located start, and the step. Only
-- 'sourceSteps' builds one, so an index is valid for its source by
-- construction.
data SourceStep = SourceStep !Int !Int !ExactPoint !CurveStep
deriving stock (Eq, Show)
instance NFData SourceStep where
rnf (SourceStep index count start step) =
rnf index `seq` rnf count `seq` rnf start `seq` rnf step
sourceStepIndex :: SourceStep -> Int
sourceStepIndex (SourceStep index _ _ _) = index
sourceStepCount :: SourceStep -> Int
sourceStepCount (SourceStep _ count _ _) = count
-- | The located start of the step.
sourceStepStart :: SourceStep -> ExactPoint
sourceStepStart (SourceStep _ _ start _) = start
sourceStepCurve :: SourceStep -> CurveStep
sourceStepCurve (SourceStep _ _ _ step) = step
-- | Which side of a join a site lies on. The end of one step and the start of
-- the next are one point; a site there is on exactly one of the two steps, at
-- parameter one before the join or at parameter zero after it. A site inside
-- a step, or at an open trail's own start or end, is away from any join; a
-- closed trail's seam is a join.
data JoinSide
= AwayFromJoin
| BeforeJoin
| AfterJoin
deriving stock (Eq, Ord, Show)
instance NFData JoinSide where
rnf side = side `seq` ()
-- | A located position on one selected step of its source. Only 'trailSite'
-- builds one; the point is the step's exact point at the parameter.
data TrailSite = TrailSite !Subpath !SourceStep !UnitInterval !ExactPoint !JoinSide
deriving stock (Eq, Show)
instance NFData TrailSite where
rnf (TrailSite source step parameter point side) =
rnf source `seq` rnf step `seq` rnf parameter `seq` rnf point `seq` rnf side
siteSource :: TrailSite -> Subpath
siteSource (TrailSite source _ _ _ _) = source
siteStep :: TrailSite -> SourceStep
siteStep (TrailSite _ step _ _ _) = step
siteStepIndex :: TrailSite -> Int
siteStepIndex = sourceStepIndex . siteStep
siteParameter :: TrailSite -> UnitInterval
siteParameter (TrailSite _ _ parameter _ _) = parameter
sitePoint :: TrailSite -> ExactPoint
sitePoint (TrailSite _ _ _ point _) = point
siteJoinSide :: TrailSite -> JoinSide
siteJoinSide (TrailSite _ _ _ _ side) = side
-- | The site's value and derivatives on its own step, relative to the step's
-- located start.
siteJet :: TrailSite -> StepJet
siteJet site = jetStep (siteParameter site) (sourceStepCurve (siteStep site))
-- | The source's steps with their indices and located starts: the one owner
-- of step selection.
sourceSteps :: Subpath -> Seq SourceStep
sourceSteps source =
Seq.mapWithIndex (\index (start, step) -> SourceStep index count start step) (Seq.zip starts steps)
where
(anchor, steps) = case source of
OpenSubpath value -> (location value, trailSteps (locatedValue value))
ClosedSubpath value -> (location value, closedTrailSteps (locatedValue value))
count = Seq.length steps
starts = Seq.scanl (\start step -> translateExactPoint start (curveStepEnd step)) anchor steps
trailSite :: Subpath -> SourceStep -> UnitInterval -> ExactPoint -> TrailSite
trailSite source step@(SourceStep index count _ _) parameter point =
TrailSite source step parameter point side
where
closed = case source of
ClosedSubpath _ -> True
OpenSubpath _ -> False
side
| parameter == unitOne && (closed || index + 1 < count) = BeforeJoin
| parameter == unitZero && (closed || index > 0) = AfterJoin
| otherwise = AwayFromJoin
-- | The site at a parameter of the source's step at an index, if the source
-- has that step.
selectSite :: Subpath -> Int -> UnitInterval -> Maybe TrailSite
selectSite source index parameter = place <$> Seq.lookup index (sourceSteps source)
where
place step = trailSite source step parameter
(translateExactPoint (sourceStepStart step) (evaluateStep parameter (sourceStepCurve step)))
-- | The jet of the other step at a site's join: the next step's start before
-- the join, the previous step's end after it. Only a closed trail's seam
-- joins its last and first steps, and 'trailSite' marks an open trail's own
-- start and end away from any join, so the neighbour index is reduced modulo
-- the count, which the site's step makes positive.
joinNeighbourJet :: TrailSite -> Maybe StepJet
joinNeighbourJet (TrailSite source (SourceStep index count _ _) _ _ side) = case side of
AwayFromJoin -> Nothing
BeforeJoin -> neighbour (index + 1) unitZero
AfterJoin -> neighbour (index - 1) unitOne
where
neighbour at parameter =
jetStep parameter . sourceStepCurve <$> Seq.lookup (at `mod` count) (sourceSteps source)
-- | One span of a source step as subdivision reaches it: the step, a bracket
-- of its parameters, the span's located start, and the piece. Only
-- 'wholeSpan' and 'halveSpan' build one, so the piece is the step restricted
-- to the bracket and the start is the step's point at the bracket's lower
-- parameter.
data SourceSpan = SourceSpan !SourceStep !UnitInterval !UnitInterval !ExactPoint !CurveStep
deriving stock (Eq, Show)
instance NFData SourceSpan where
rnf (SourceSpan step from to start piece) =
rnf step `seq` rnf from `seq` rnf to `seq` rnf start `seq` rnf piece
wholeSpan :: SourceStep -> SourceSpan
wholeSpan step = SourceSpan step unitZero unitOne (sourceStepStart step) (sourceStepCurve step)
-- | The span's two halves: exact de Casteljau subdivision at the piece's own
-- midpoint, which is the bracket's midpoint in the step's parameter.
halveSpan :: SourceSpan -> (SourceSpan, SourceSpan)
halveSpan (SourceSpan step from to start piece) =
( SourceSpan step from middle start left
, SourceSpan step middle to (translateExactPoint start (curveStepEnd left)) right )
where
(left, right) = splitStep unitHalf piece
middle = unitMidpoint from to
sourceSpanStep :: SourceSpan -> SourceStep
sourceSpanStep (SourceSpan step _ _ _ _) = step
sourceSpanFrom :: SourceSpan -> UnitInterval
sourceSpanFrom (SourceSpan _ from _ _ _) = from
sourceSpanTo :: SourceSpan -> UnitInterval
sourceSpanTo (SourceSpan _ _ to _ _) = to
sourceSpanStart :: SourceSpan -> ExactPoint
sourceSpanStart (SourceSpan _ _ _ start _) = start
sourceSpanPiece :: SourceSpan -> CurveStep
sourceSpanPiece (SourceSpan _ _ _ _ piece) = piece
-- | The piece's controls, located at the span's start: the hull a caller
-- retains, queries and compares.
sourceSpanControls :: SourceSpan -> NonEmpty ExactPoint
sourceSpanControls (SourceSpan _ _ _ start piece) = translateExactPoint start <$> stepControlPoints piece
-- | Admit a span under the budget's bits, the one gate every consumer of a
-- span passes: its located start, parameters, relative controls and weights,
-- and its located controls, whose width the relative ones do not bound.
admitSpan :: SubdivisionBudget -> SourceSpan -> Either BudgetObligation SourceSpan
admitSpan budget sourceSpan@(SourceSpan _ from to start piece)
| width > budgetBits budget = Left (BitsExhausted width)
| otherwise = Right sourceSpan
where
width = foldr (max . exactPointBitWidth) (spanBits start from to piece) (sourceSpanControls sourceSpan)
-- | The widest of a span's located start, parameters, controls and rational
-- weights.
spanBits :: ExactPoint -> UnitInterval -> UnitInterval -> CurveStep -> Int
spanBits from t0 t1 step =
foldr (max . exactRationalBitWidth) (exactPointBitWidth from)
(unitIntervalValue t0 : unitIntervalValue t1 : weights <> controls)
where
controls = concatMap (\(ExactVector x y) -> [x, y]) (stepControlPoints step)
weights = case shapeView (curveStepShape step) of
RationalQuadraticView _ a b -> [positiveExactValue a, positiveExactValue b]
_ -> []