fix brush stroke point order for open brush path

* also: add some preset brushes
This commit is contained in:
sheaf 2020-08-29 19:41:07 +02:00
parent 62bb1451c5
commit 9bde44ed42
4 changed files with 107 additions and 72 deletions

View file

@ -98,7 +98,8 @@ executable MetaBrush
Main.hs Main.hs
other-modules: other-modules:
MetaBrush.Asset.Colours MetaBrush.Asset.Brushes
, MetaBrush.Asset.Colours
, MetaBrush.Asset.Cursor , MetaBrush.Asset.Cursor
, MetaBrush.Asset.InfoBar , MetaBrush.Asset.InfoBar
, MetaBrush.Asset.Logo , MetaBrush.Asset.Logo

View file

@ -58,6 +58,8 @@ import Math.Bezier.Stroke
( StrokePoint(..) ) ( StrokePoint(..) )
import Math.Vector2D import Math.Vector2D
( Point2D(..) ) ( Point2D(..) )
import MetaBrush.Asset.Brushes
( ellipse, rect )
import MetaBrush.Asset.Colours import MetaBrush.Asset.Colours
( getColours ) ( getColours )
import MetaBrush.Asset.Logo import MetaBrush.Asset.Logo
@ -102,7 +104,7 @@ testDocuments = IntMap.fromList
{ displayName = "Document 1" { displayName = "Document 1"
, filePath = Nothing , filePath = Nothing
, unsavedChanges = False , unsavedChanges = False
, strokes = [ Stroke ( circle ( PointData Normal ( rect $ BrushPointData Normal ) ) ) "Circle" True ( unsafeUnique 0 ) , strokes = [ Stroke ( ellipse 150 100 ( PointData Normal ( rect 30 6 $ BrushPointData Normal ) ) ) "Ellipse" True ( unsafeUnique 0 )
] ]
, bounds = AABB ( Point2D 0 0 ) ( Point2D 100 100 ) , bounds = AABB ( Point2D 0 0 ) ( Point2D 100 100 )
, viewportCenter = Point2D 50 50 , viewportCenter = Point2D 50 50
@ -112,62 +114,20 @@ testDocuments = IntMap.fromList
{ displayName = "Document 2" { displayName = "Document 2"
, filePath = Nothing , filePath = Nothing
, unsavedChanges = True , unsavedChanges = True
, strokes = [ ] , strokes = [ Stroke linePts "Line" True ( unsafeUnique 1 ) ]
, bounds = AABB ( Point2D 0 0 ) ( Point2D 50 50 ) , bounds = AABB ( Point2D 0 0 ) ( Point2D 50 50 )
, viewportCenter = Point2D 10 10 , viewportCenter = Point2D 0 0
, zoomFactor = 0.25 , zoomFactor = 1
} }
] ]
circle :: forall a. a -> Seq ( StrokePoint a )
circle d = Seq.fromList
[ pp ( Point2D 0 1 )
, cp ( Point2D a 1 )
, cp ( Point2D 1 a )
, pp ( Point2D 1 0 )
, cp ( Point2D 1 (-a) )
, cp ( Point2D a (-1) )
, pp ( Point2D 0 (-1) )
, cp ( Point2D (-a) (-1) )
, cp ( Point2D (-1) (-a) )
, pp ( Point2D (-1) 0 )
, cp ( Point2D (-1) a )
, cp ( Point2D (-a) 1 )
, pp ( Point2D 0 1 )
]
where where
a :: Double linePts :: Seq ( StrokePoint PointData )
a = 0.551915024494 linePts = Seq.fromList
pp, cp :: Point2D Double -> StrokePoint a [ PathPoint ( Point2D 0 (-100) ) ( PointData Normal ( ellipse 30 8 $ BrushPointData Normal ) )
pp p = PathPoint ( fmap ( * 100 ) p ) d , ControlPoint ( Point2D 0 ( -30) ) ( PointData Normal ( ellipse 25 6 $ BrushPointData Normal ) )
cp p = ControlPoint ( fmap ( * 100 ) p ) d , ControlPoint ( Point2D 0 ( 30) ) ( PointData Normal ( ellipse 15 6 $ BrushPointData Normal ) )
, PathPoint ( Point2D 0 ( 100) ) ( PointData Normal ( ellipse 5 2 $ BrushPointData Normal ) )
razor :: forall a. a -> Seq ( StrokePoint a ) ]
razor d = Seq.fromList
[ pp ( Point2D 30 0 )
, cp ( Point2D 30 -6 )
, cp ( Point2D -30 -6 )
, pp ( Point2D -30 0 )
, cp ( Point2D -30 3 )
, cp ( Point2D 30 3 )
, pp ( Point2D 30 0 )
]
where
pp, cp :: Point2D Double -> StrokePoint a
pp p = PathPoint p d
cp p = ControlPoint p d
rect :: forall a. a -> Seq ( StrokePoint a )
rect d = Seq.fromList
[ pp ( Point2D 20 5 )
, pp ( Point2D 20 -5 )
, pp ( Point2D -20 -5 )
, pp ( Point2D -20 5 )
, pp ( Point2D 20 5 )
]
where
pp :: Point2D Double -> StrokePoint a
pp p = PathPoint p d
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View file

@ -0,0 +1,70 @@
{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE ScopedTypeVariables #-}
module MetaBrush.Asset.Brushes
( ellipse, blob, rect )
where
-- containers
import Data.Sequence
( Seq(..) )
import qualified Data.Sequence as Seq
( fromList )
-- MetaBrush
import Math.Bezier.Stroke
( StrokePoint(..) )
import Math.Vector2D
( Point2D(..) )
--------------------------------------------------------------------------------
ellipse :: forall d. Double -> Double -> d -> Seq ( StrokePoint d )
ellipse w h d = Seq.fromList
[ pp ( Point2D 0 1 )
, cp ( Point2D a 1 )
, cp ( Point2D 1 a )
, pp ( Point2D 1 0 )
, cp ( Point2D 1 (-a) )
, cp ( Point2D a (-1) )
, pp ( Point2D 0 (-1) )
, cp ( Point2D (-a) (-1) )
, cp ( Point2D (-1) (-a) )
, pp ( Point2D (-1) 0 )
, cp ( Point2D (-1) a )
, cp ( Point2D (-a) 1 )
, pp ( Point2D 0 1 )
]
where
a :: Double
a = 0.551915024494
pp, cp :: Point2D Double -> StrokePoint d
pp ( Point2D x y ) = PathPoint ( Point2D ( w * x ) ( h * y ) ) d
cp ( Point2D x y ) = ControlPoint ( Point2D ( w * x ) ( h * y ) ) d
blob :: forall d. Double -> Double -> d -> Seq ( StrokePoint d )
blob w h d = Seq.fromList
[ pp ( Point2D 1 0 )
, cp ( Point2D 1 -1 )
, cp ( Point2D -1 -1 )
, pp ( Point2D -1 0 )
, cp ( Point2D -1 1 )
, cp ( Point2D 1 1 )
, pp ( Point2D 1 0 )
]
where
pp, cp :: Point2D Double -> StrokePoint d
pp ( Point2D x y ) = PathPoint ( Point2D ( w * x ) ( h * y ) ) d
cp ( Point2D x y ) = ControlPoint ( Point2D ( w * x ) ( h * y ) ) d
rect :: forall d. Double -> Double -> d -> Seq ( StrokePoint d )
rect w h d = Seq.fromList
[ pp ( Point2D 1 1 )
, pp ( Point2D 1 -1 )
, pp ( Point2D -1 -1 )
, pp ( Point2D -1 1 )
, pp ( Point2D 1 1 )
]
where
pp :: Point2D Double -> StrokePoint d
pp ( Point2D x y ) = PathPoint ( Point2D ( w * x ) ( h * y ) ) d

View file

@ -115,18 +115,17 @@ stroke allPts@( spt0 :<| spt1 :<| spts )
| isClosed | isClosed
= Right ( fwdPts, bwdPts ) = Right ( fwdPts, bwdPts )
| otherwise | otherwise
= Left ( fwdPts <> bwdPts ) = Left ( startingCap <> fwdPts <> bwdPts )
where where
startOffset, endOffset :: Vector2D Double startOffset, endOffset :: Vector2D Double
tgt_start, tgt_end :: Vector2D Double tgt_start, tgt_end :: Vector2D Double
brush_start, brush_end :: Seq ( StrokePoint x )
startOffset = Point2D 0 0 --> coords spt0 startOffset = Point2D 0 0 --> coords spt0
tgt_start = coords spt0 --> coords spt1 tgt_start = coords spt0 --> coords spt1
( tgt_end, endOffset ) = case allPts of ( tgt_end, endOffset, brush_end ) = case allPts of
_ :|> sptnm1 :|> sptn -> ( coords sptnm1 --> coords sptn, Point2D 0 0 --> coords sptn ) _ :|> sptnm1 :|> sptn -> ( coords sptnm1 --> coords sptn, Point2D 0 0 --> coords sptn, brushShape sptn )
_ -> error "impossible" _ -> error "impossible"
brush_start :: Seq ( StrokePoint x )
brush_start = brushShape spt0 brush_start = brushShape spt0
isClosed :: Bool isClosed :: Bool
@ -149,19 +148,6 @@ stroke allPts@( spt0 :<| spt1 :<| spts )
-- Connecting paths at a point of discontinuity of the tangent vector direction (G1 discontinuity). -- Connecting paths at a point of discontinuity of the tangent vector direction (G1 discontinuity).
-- This happens at corners of the brush path (including endpoints of an open brush path, where the tangent flips direction). -- This happens at corners of the brush path (including endpoints of an open brush path, where the tangent flips direction).
joinAndContinue :: Vector2D Double -> StrokePoint d -> Seq ( StrokePoint d ) -> ( Seq ( StrokePoint () ), Seq ( StrokePoint () ) ) joinAndContinue :: Vector2D Double -> StrokePoint d -> Seq ( StrokePoint d ) -> ( Seq ( StrokePoint () ), Seq ( StrokePoint () ) )
joinAndContinue _ _ Empty
-- Closed curve.
| isClosed
= if parallel tgt_start tgt_end
then ( Empty, Empty )
else ( startOffset joinWithBrush ( withTangent tgt_start brush_start ) ( withTangent tgt_end brush_start ) brush_start
, startOffset joinWithBrush ( withTangent ( (-1) *^ tgt_start ) brush_start ) ( withTangent ( (-1) *^ tgt_end ) brush_start ) brush_start
)
-- Open curve.
| otherwise
= ( endOffset joinWithBrush ( withTangent tgt_end brush_start ) ( withTangent ( (-1) *^ tgt_end ) brush_start ) brush_start
, startOffset joinWithBrush ( withTangent ( (-1) *^ tgt_start ) brush_start ) ( withTangent tgt_start brush_start ) brush_start
)
joinAndContinue tgt sp0 ( sp1 :<| sps ) joinAndContinue tgt sp0 ( sp1 :<| sps )
| tgt' `parallel` tgt | tgt' `parallel` tgt
= go sp0 ( sp1 :<| sps ) = go sp0 ( sp1 :<| sps )
@ -177,6 +163,24 @@ stroke allPts@( spt0 :<| spt1 :<| spts )
tgt' = coords sp0 --> coords sp1 tgt' = coords sp0 --> coords sp1
brush0 :: Seq ( StrokePoint () ) brush0 :: Seq ( StrokePoint () )
brush0 = removePointData $ brushShape @x sp0 brush0 = removePointData $ brushShape @x sp0
joinAndContinue _ _ Empty
-- Closed curve.
| isClosed
= if parallel tgt_start tgt_end
then ( Empty, Empty )
else ( startOffset joinWithBrush ( withTangent tgt_start brush_start ) ( withTangent tgt_end brush_start ) brush_start
, startOffset joinWithBrush ( withTangent ( (-1) *^ tgt_start ) brush_start ) ( withTangent ( (-1) *^ tgt_end ) brush_start ) brush_start
)
-- Open curve.
| otherwise
= ( endOffset joinWithBrush ( withTangent tgt_end brush_end ) ( withTangent ( (-1) *^ tgt_end ) brush_end ) brush_end
, Empty -- handled separately: see 'startingCap' below
)
-- Final cap for an open curve. Handled separately for correct stroke order.
startingCap :: Seq ( StrokePoint () )
startingCap
= startOffset joinWithBrush ( withTangent ( (-1) *^ tgt_start ) brush_start ) ( withTangent tgt_start brush_start ) brush_start
go :: StrokePoint d -> Seq ( StrokePoint d ) -> ( Seq ( StrokePoint () ), Seq ( StrokePoint () ) ) go :: StrokePoint d -> Seq ( StrokePoint d ) -> ( Seq ( StrokePoint () ), Seq ( StrokePoint () ) )
go _ Empty = ( Empty, Empty ) go _ Empty = ( Empty, Empty )