generative-art-0.1.0.0
Safe HaskellSafe-Inferred
LanguageHaskell2010

Geometry.Core

Synopsis

Primitives

2D Vectors

data Vec2 Source #

Constructors

Vec2 !Double !Double 

Instances

Instances details
Show Vec2 Source # 
Instance details

Defined in Geometry.Core

Methods

showsPrec :: Int -> Vec2 -> ShowS #

show :: Vec2 -> String #

showList :: [Vec2] -> ShowS #

NFData Vec2 Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Vec2 -> () #

VectorSpace Vec2 Source # 
Instance details

Defined in Geometry.Core

ChaosSource Vec2 Source # 
Instance details

Defined in Geometry.Chaotic

Methods

perturb :: Vec2 -> Int Source #

MwcChaosSource Vec2 Source # 
Instance details

Defined in Geometry.Chaotic

Methods

mwcChaos :: Vec2 -> Word32 Source #

HasBoundingBox Vec2 Source # 
Instance details

Defined in Geometry.Core

Transform Vec2 Source # 
Instance details

Defined in Geometry.Core

Eq Vec2 Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Vec2 -> Vec2 -> Bool #

(/=) :: Vec2 -> Vec2 -> Bool #

Ord Vec2 Source # 
Instance details

Defined in Geometry.Core

Methods

compare :: Vec2 -> Vec2 -> Ordering #

(<) :: Vec2 -> Vec2 -> Bool #

(<=) :: Vec2 -> Vec2 -> Bool #

(>) :: Vec2 -> Vec2 -> Bool #

(>=) :: Vec2 -> Vec2 -> Bool #

max :: Vec2 -> Vec2 -> Vec2 #

min :: Vec2 -> Vec2 -> Vec2 #

UniformRange Vec2 Source # 
Instance details

Defined in Geometry.Core

Methods

uniformRM :: StatefulGen g m => (Vec2, Vec2) -> g -> m Vec2

dotProduct :: Vec2 -> Vec2 -> Double Source #

Dot product.

\[ \begin{pmatrix}a \\ b\end{pmatrix} \cdot \begin{pmatrix}x \\ y\end{pmatrix}\ = ax + by\]

Can be used to check whether two vectors point into the same direction (product > 0).

norm :: Vec2 -> Double Source #

Euclidean norm.

\[ \|\mathbf v\| = \sqrt{v_x^2 + v_y^2} \]

normSquare :: Vec2 -> Double Source #

Squared Euclidean norm. Does not require a square root, and is thus suitable for sorting points by distance without excluding certain kinds of numbers such as rationals.

\[ \|\mathbf v\|^2 = v_x^2 + v_y^2 \]

polar :: Angle -> Double -> Vec2 Source #

Construct a Vec2 from polar coordinates.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/polar.svg" 100 60 $ \_ -> do
    let angle = deg 30
        p = polar angle 100
    cairoScope $ do
        setColor (mma 1)
        C.arc 0 0 40 0 (getRad angle)
        sketch (Line zero p)
        C.stroke
    cairoScope $ do
        setColor (mma 0)
        sketch (Circle p 3)
        C.fill
:}
Generated file: size 2KB, crc32: 0x7a43a888

Lines

data Line Source #

Line, defined by beginning and end.

Constructors

Line !Vec2 !Vec2 

Instances

Instances details
Show Line Source # 
Instance details

Defined in Geometry.Core

Methods

showsPrec :: Int -> Line -> ShowS #

show :: Line -> String #

showList :: [Line] -> ShowS #

NFData Line Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Line -> () #

Sketch Line Source #

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Line.svg" 150 100 $ \_ -> do
    C.setLineWidth 2
    sketch (Line (Vec2 10 10) (Vec2 140 90))
    stroke
:}
Generated file: size 2KB, crc32: 0x9287e4a8
Instance details

Defined in Draw

Methods

sketch :: Line -> Render () Source #

Plotting Line Source # 
Instance details

Defined in Draw.Plotting

Methods

plot :: Line -> Plot () Source #

ChaosSource Line Source # 
Instance details

Defined in Geometry.Chaotic

Methods

perturb :: Line -> Int Source #

MwcChaosSource Line Source # 
Instance details

Defined in Geometry.Chaotic

Methods

mwcChaos :: Line -> Word32 Source #

HasBoundingBox Line Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_line.svg" 150 100 $ \_ -> do
    let line = Line (Vec2 10 10) (Vec2 140 90)
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox line)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch line
        C.stroke
:}
Generated file: size 2KB, crc32: 0xd5f4f645
Instance details

Defined in Geometry.Core

Transform Line Source # 
Instance details

Defined in Geometry.Core

Eq Line Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Line -> Line -> Bool #

(/=) :: Line -> Line -> Bool #

Ord Line Source # 
Instance details

Defined in Geometry.Core

Methods

compare :: Line -> Line -> Ordering #

(<) :: Line -> Line -> Bool #

(<=) :: Line -> Line -> Bool #

(>) :: Line -> Line -> Bool #

(>=) :: Line -> Line -> Bool #

max :: Line -> Line -> Line #

min :: Line -> Line -> Line #

angleOfLine :: Line -> Angle Source #

Angle of a single line, relative to the x axis.

For sorting by angle, use the faster pseudoAngle!

angleBetween :: Line -> Line -> Angle Source #

Angle between two lines.

The result depends on the direction of the lines; use lineReverse if necessary.

angledLine Source #

Arguments

:: Vec2

Start

-> Angle 
-> Double

Length

-> Line 

moveAlongLine Source #

Arguments

:: Line 
-> Double

Distance

-> Vec2 

Where do you end up when walking Distance on a Line?

moveAlong (Line start end) 0 == start
moveAlong (Line start end) (lineLength …) == end

resizeLine :: (Double -> Double) -> Line -> Line Source #

Resize a line, keeping the starting point.

resizeLineSymmetric :: (Double -> Double) -> Line -> Line Source #

Resize a line, keeping the middle point.

centerLine :: Line -> Line Source #

Move the line so that its center is where the start used to be.

Useful for painting lines going through a point symmetrically.

normalizeLine :: Line -> Line Source #

Move the end point of the line so that it has length 1.

lineReverse :: Line -> Line Source #

Switch defining points of a line.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/line_reverse.svg" 180 150 $ \_ -> do
    let line = Line (Vec2 10 10) (Vec2 150 140)
        line' = lineReverse line
    C.setLineWidth 2
    cairoScope $ do
        sketch (Arrow line def)
        C.stroke
    cairoScope $ do
        setColor (mma 1)
        C.translate 20 0
        sketch (Arrow line' def)
        C.stroke
:}
Generated file: size 2KB, crc32: 0x2e031609

perpendicularBisector :: Line -> Line Source #

The result has the same length as the input, point in its center, and points to the left (90° turned CCW) relative to the input.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/perpendicular_bisector.svg" 200 160 $ \_ -> do
    let line = Line (Vec2 20 20) (Vec2 190 90)
        bisector = perpendicularBisector line
    C.setLineWidth 2
    sketch line >> C.stroke
    sketch bisector >> setColor (mma 1) >> C.stroke
:}
Generated file: size 2KB, crc32: 0x9940c32d

perpendicularLineThrough :: Vec2 -> Line -> Line Source #

Line perpendicular to a given line through a point, starting at the intersection point and pointing away from the line.

If the point is on the line directly, fall back to a perpendicular line through the point of half the length of the input.

This is also known as the vector projection. For vectors \(\mathbf a\) (pointing from the start of the line to \(\mathbf p\)) and \(\mathbf b\) (pointing from the start of the line to its end),

\[ \mathrm{proj}_{\mathbf b}(\mathbf a) = \frac{\mathbf a\cdot\mathbf b}{\mathbf b\cdot\mathbf b}\mathbf b \]

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/perpendicular_line_through.svg" 170 170 $ \_ -> do
    let line = transform (translate (Vec2 20 20))
                         (Line zero (Vec2 (3*40) (4*20)))
        points =
            [ Vec2 20 110  -- above
            , Vec2 70 90   -- above
            , Vec2 130 20  -- below
            , Vec2 110 80  -- directly on
            , Vec2 130 150 -- beyond
            ]
    C.setLineWidth 2
    sketch line >> C.stroke
    for_ (zip [1..] points) $ \(i, p) -> cairoScope $ do
        setColor (mma i)
        sketch (Arrow (perpendicularLineThrough p line) def)
        C.stroke
        sketch (Circle p 5)
        C.strokePreserve
        setColor (mma i `withOpacity` 0.7)
        C.fill
:}
Generated file: size 6KB, crc32: 0xf5ff8529

distanceFromLine :: Vec2 -> Line -> Double Source #

Distance of a point from a line.

intersectInfiniteLines :: Line -> Line -> Vec2 Source #

Intersection of two infinite lines. Fast, but does not provide any error handling (such as the parallel inputs case) and does not provide any detail about the nature of the intersection point.

Use intersectionLL for more detailed information, at the cost of computational complexity.

data LLIntersection Source #

Constructors

IntersectionReal Vec2

Two lines intersect fully.

IntersectionVirtualInsideL Vec2

The intersection is in the left argument (of intersectionLL) only, and only on the infinite continuation of the right argument.

IntersectionVirtualInsideR Vec2

dito, but the other way round.

IntersectionVirtual Vec2

The intersection lies in the infinite continuations of both lines.

Parallel

Lines are parallel.

Collinear (Maybe Line)

Lines are collinear, and maybe overlap along a Line segment.

intersectionLL :: Line -> Line -> LLIntersection Source #

Calculate the intersection of two lines.

Returns the point of the intersection, and whether it is inside both, one, or none of the provided finite line segments.

See intersectInfiniteLines for a more performant, but less nuanced result.

intersectionPoint :: LLIntersection -> Maybe Vec2 Source #

The single point of intersection of two lines, or Nothing for none (collinear).

subdivideLine Source #

Arguments

:: Int

Number of segments

-> Line 
-> [Vec2] 

Subdivide a line into a number of equally long parts. Useful for distorting straight lines. The first and last points are (exactly) equal to the start and end of the input line.

See also subdivideLineByLength.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/subdivide_line.svg" 200 150 $ \_ -> do
    let line = Line (Vec2 20 20) (Vec2 190 140)
        subdivisions = subdivideLine 8 line
    cairoScope $ do
        C.setLineWidth 2
        setColor (mma 0)
        sketch line
        C.stroke
    cairoScope $ for_ subdivisions $ \point -> do
        sketch (Circle point 4)
        setColor (mma 1)
        C.fill
:}
Generated file: size 4KB, crc32: 0x527103e6

subdivideLineByLength Source #

Arguments

:: Double

Maximum segment length. All segments will have the same length; to accomplish this, this value is an upper bound.

-> Line 
-> [Vec2] 

Subdivide a line into evenly-sized intervals of a maximum length. Useful for distorting straight lines. The first and last points are (exactly) equal to the start and end of the input line.

See also 'subdivideLine.'.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/subdivide_line_by_length.svg" 200 150 $ \_ -> do
    let line = Line (Vec2 20 20) (Vec2 190 140)
        subdivisions = subdivideLineByLength 30 line
    cairoScope $ do
        C.setLineWidth 2
        setColor (mma 0)
        sketch line
        C.stroke
    cairoScope $ for_ subdivisions $ \point -> do
        sketch (Circle point 4)
        setColor (mma 1)
        C.fill
:}
Generated file: size 4KB, crc32: 0x6180122d

reflection Source #

Arguments

:: Line

Light ray

-> Line

Mirror

-> Maybe (Line, Vec2, LLIntersection)

Reflected ray; point of incidence; type of intersection of the ray with the mirror. The reflected ray is symmetric with respect to the incoming ray (in terms of length, distance from mirror, etc.), but has reversed direction (like real light).

Optical reflection of a ray on a mirror. Note that the outgoing line has reversed direction like light rays would. The second result element is the point of intersection with the mirror, which is not necessarily on the line, and thus returned separately.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/reflection.svg" 520 300 $ \_ -> do
   let mirrorSurface = angledLine (Vec2 10 100) (deg 10) 510
   cairoScope $ do
       C.setLineWidth 2
       setColor (black `withOpacity` 0.5)
       sketch mirrorSurface
       C.stroke
   let angles = [-135,-120.. -10]
   cairoScope $ do
       let rayOrigin = Vec2 180 250
       setColor (hsv 0 1 0.7)
       sketch (Circle rayOrigin 5)
       C.stroke
       for_ angles $ \angleDeg -> do
           let rayRaw = angledLine rayOrigin (deg angleDeg) 100
               Just (Line _ reflectedRayEnd, iPoint, _) = reflection rayRaw mirrorSurface
               ray = Line rayOrigin iPoint
               ray' = Line iPoint reflectedRayEnd
           setColor (flare (lerp (minimum angles, maximum angles) (0.2,0.8) angleDeg))
           sketch ray
           sketch ray'
           C.stroke
   cairoScope $ do
       let rayOrigin = Vec2 350 30
       setColor (hsva 180 1 0.7 1)
       sketch (Circle rayOrigin 5)
       C.stroke
       for_ angles $ \angleDeg -> do
           let rayRaw = angledLine rayOrigin (deg angleDeg) 100
               Just (Line _ reflectedRayEnd, iPoint, _) = reflection rayRaw mirrorSurface
               ray = Line rayOrigin iPoint
               ray' = Line iPoint reflectedRayEnd
           setColor (crest (lerp (minimum angles, maximum angles) (0,1) angleDeg))
           sketch ray
           sketch ray'
           C.stroke
:}
Generated file: size 9KB, crc32: 0x76885f25

Polylines

newtype Polyline Source #

Explicit type for polylines. Useful in type signatures, beacuse [[[Vec2]]] is really hard to read. Also makes some typeclass instances clearer, such as sketch.

Constructors

Polyline [Vec2] 

Instances

Instances details
Show Polyline Source # 
Instance details

Defined in Geometry.Core

NFData Polyline Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Polyline -> () #

Sketch Polyline Source #

Polyline, i.e. a sequence of lines given by their joints.

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Sequential_Vec2.svg" 150 100 $ \_ -> do
    C.setLineWidth 2
    sketch (Polyline [Vec2 10 10, Vec2 90 90, Vec2 120 10, Vec2 140 50])
    stroke
:}
Generated file: size 2KB, crc32: 0x5d5a0158
Instance details

Defined in Draw

Methods

sketch :: Polyline -> Render () Source #

Plotting Polyline Source # 
Instance details

Defined in Draw.Plotting

Methods

plot :: Polyline -> Plot () Source #

HasBoundingBox Polyline Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_polyline.svg" 150 100 $ \_ -> do
    let polyline = Polyline [Vec2 10 10, Vec2 90 90, Vec2 120 10, Vec2 140 50]
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox polyline)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch polyline
        C.stroke
:}
Generated file: size 2KB, crc32: 0xd45fc8b5
Instance details

Defined in Geometry.Core

Transform Polyline Source # 
Instance details

Defined in Geometry.Core

Eq Polyline Source # 
Instance details

Defined in Geometry.Core

Ord Polyline Source # 
Instance details

Defined in Geometry.Core

polylineLength :: Polyline -> Double Source #

Total length of a Polyline.

>>> polylineLength (Polyline [zero, Vec2 123.4 0])
123.4

polylineEdges :: Polyline -> [Line] Source #

All lines composing a Polyline (in order).

>>> polylineEdges (Polyline [zero, Vec2 50 50, Vec2 100 0])
[Line (Vec2 0.0 0.0) (Vec2 50.0 50.0),Line (Vec2 50.0 50.0) (Vec2 100.0 0.0)]

Polygons

newtype Polygon Source #

Polygon, defined by its corners.

Many algorithms assume certain invariants about polygons, see validatePolygon for details.

Constructors

Polygon [Vec2] 

Instances

Instances details
Show Polygon Source # 
Instance details

Defined in Geometry.Core

NFData Polygon Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Polygon -> () #

Sketch Polygon Source #

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Polygon.svg" 100 100 $ \_ -> do
    C.setLineWidth 2
    sketch (Polygon [Vec2 20 10, Vec2 10 80, Vec2 45 45, Vec2 60 90, Vec2 90 30])
    stroke
:}
Generated file: size 2KB, crc32: 0x7f620554
Instance details

Defined in Draw

Methods

sketch :: Polygon -> Render () Source #

Plotting Polygon Source # 
Instance details

Defined in Draw.Plotting

Methods

plot :: Polygon -> Plot () Source #

ChaosSource Polygon Source # 
Instance details

Defined in Geometry.Chaotic

Methods

perturb :: Polygon -> Int Source #

MwcChaosSource Polygon Source # 
Instance details

Defined in Geometry.Chaotic

HasBoundingBox Polygon Source # 
Instance details

Defined in Geometry.Core

Transform Polygon Source # 
Instance details

Defined in Geometry.Core

Eq Polygon Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Polygon -> Polygon -> Bool #

(/=) :: Polygon -> Polygon -> Bool #

Ord Polygon Source # 
Instance details

Defined in Geometry.Core

normalizePolygon :: Polygon -> Polygon Source #

List-rotate the polygon’s corners until the minimum is the first entry in the corner list.

validatePolygon :: Polygon -> Either PolygonError Polygon Source #

Check whether the polygon satisfies the invariants assumed by many algorithms,

  • At least three corners
  • No identical points
  • No self-intersections

Returns the provided polygon on success.

pointInPolygon :: Vec2 -> Polygon -> Bool Source #

Is the point inside the polygon?

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/point_in_polygon.svg" 180 180 $ \_ -> do
    let square = boundingBoxPolygon (shrinkBoundingBox 40 [zero, Vec2 180 180])
        points = subdivideLine 11 (Line (Vec2 20 120) (Vec2 160 60))
    C.setLineWidth 2
    sketch square
    C.stroke
    setColor (mma 1)
    for_ points $ \point -> do
        sketch (Circle point 4)
        if pointInPolygon point square then C.fill else C.stroke
:}
Generated file: size 5KB, crc32: 0xabb5dfc

polygonAverage :: Polygon -> Vec2 Source #

Average of polygon vertices. Note that this is not the same as polygonCentroid, which is much less influenced by clustered corners.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/polygon_average.svg" 100 100 $ \_ -> do
    let polygon = Polygon [Vec2 10 10, Vec2 10 90, Vec2 20 70, Vec2 40 60, Vec2 30 40, Vec2 90 90, Vec2 80 20]
        averate = polygonAverage polygon
    sketch polygon
    C.stroke
    setColor (mma 1)
    sketch (Circle averate 5)
    sketch (Cross averate 5)
    C.stroke
:}
Generated file: size 2KB, crc32: 0xcf7a8233

polygonCentroid :: Polygon -> Vec2 Source #

The centroid or geometric center of mass of a polygon.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/polygon_centroid.svg" 100 100 $ \_ -> do
    let polygon = Polygon [Vec2 10 10, Vec2 10 90, Vec2 20 70, Vec2 40 60, Vec2 30 40, Vec2 90 90, Vec2 80 20]
        centroid = polygonCentroid polygon
    sketch polygon
    C.stroke
    setColor (mma 1)
    sketch (Circle centroid 5)
    sketch (Cross centroid 5)
    C.stroke
:}
Generated file: size 2KB, crc32: 0x4453ccc1

polygonCircumference :: Polygon -> Double Source #

Sum of all edge lengths.

polygonArea :: Polygon -> Double Source #

Area of a polygon.

signedPolygonArea :: Polygon -> Double Source #

Area of a polygon. The result’s sign depends on orientation: PolygonPositive Polygons have positive area.

>>> signedPolygonArea (Polygon [Vec2 0 0, Vec2 10 0, Vec2 10 10, Vec2 0 10])
100.0
>>> signedPolygonArea (Polygon [Vec2 0 0, Vec2 0 10, Vec2 10 10, Vec2 10 0])
-100.0

polygonEdges :: Polygon -> [Line] Source #

All the polygon’s edges, in order, starting at an arbitrary corner.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/polygon_edges.svg" 100 100 $ \_ -> do
    let polygon = Polygon [ transform (rotateAround (Vec2 50 50) (deg d)) (Vec2 50 10) | d <- take 5 [0, 360/5 ..] ]
    for_ (zip [0..] (polygonEdges polygon)) $ \(i, edge) -> do
        C.setLineCap C.LineCapRound
        setColor (mma i)
        sketch edge
        C.stroke
:}
Generated file: size 3KB, crc32: 0x9c27134b

polygonAngles :: Polygon -> [Angle] Source #

All interior angles, in order, starting at an arbitrary corner.

isConvex :: Polygon -> Bool Source #

Check whether the polygon is convex.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/is_convex.svg" 200 100 $ \_ -> do
    let convex = Polygon [Vec2 10 10, Vec2 10 90, Vec2 90 90, Vec2 90 10]
        concave = Polygon [Vec2 110 10, Vec2 110 90, Vec2 150 50, Vec2 190 90, Vec2 190 10]
    for_ [convex, concave] $ \polygon -> do
        if isConvex polygon
            then setColor (mma 2)
            else setColor (mma 3)
        sketch polygon
        C.stroke
:}
Generated file: size 2KB, crc32: 0x60a3e9a1

convexHull :: Foldable list => list Vec2 -> Polygon Source #

The smallest convex polygon that contains all points.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/convex_hull.svg" 200 200 $ \(Vec2 w h) -> do
    points <- C.liftIO $ MWC.withRng [] $ \gen ->
        gaussianDistributedPoints
            gen
            (shrinkBoundingBox 10 [zero, Vec2 w h])
            (30 *. mempty)
            100
    for_ points $ \point -> do
        sketch (Circle point 3)
        C.fill
    setColor (mma 1)
    for_ (polygonEdges (convexHull points)) $ \edge ->
        sketch (Arrow edge def{_arrowheadRelPos=0.5, _arrowheadSize=5})
    C.stroke
:}
Generated file: size 29KB, crc32: 0xe4bf922

polygonOrientation :: Polygon -> PolygonOrientation Source #

>>> polygonOrientation (Polygon [Vec2 0 0, Vec2 100 0, Vec2 100 100, Vec2 0 100])
PolygonPositive
>>> polygonOrientation (Polygon [Vec2 0 0, Vec2 0 100, Vec2 100 100, Vec2 100 0])
PolygonNegative

growPolygon :: Double -> Polygon -> Polygon Source #

Move all edges of a polygon outwards by the specified amount. Negative values shrink instead (or use shrinkPolygon).

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/grow_polygon.svg" 160 230 $ \_ -> do
    let polygon = transform (scale 2) $ Polygon [Vec2 20 40, Vec2 20 80, Vec2 40 60, Vec2 60 80, Vec2 60 40, Vec2 40 20]
    for_ [0,5..25] $ \offset -> cairoScope $ do
        when (offset == 0) (C.setLineWidth 3)
        setColor (icefire (Numerics.Interpolation.lerp (0,25) (0.5, 1) (fromIntegral offset)))
        sketch (growPolygon (fromIntegral offset) polygon)
        C.stroke
:}
Generated file: size 4KB, crc32: 0xd6cd58c1

shrinkPolygon :: Double -> Polygon -> Polygon Source #

Convenience version of growPolygon for negative deltas.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/shrink_polygon.svg" 160 230 $ \_ -> do
    let polygon = transform (scale 2) $ Polygon [Vec2 20 40, Vec2 20 80, Vec2 40 60, Vec2 60 80, Vec2 60 40, Vec2 40 20]
    for_ [0,5..25] $ \offset -> cairoScope $ do
        when (offset == 0) (C.setLineWidth 3)
        setColor (icefire (Numerics.Interpolation.lerp (0,25) (0.5, 0) (fromIntegral offset)))
        sketch (shrinkPolygon (fromIntegral offset) polygon)
        C.stroke
:}
Generated file: size 4KB, crc32: 0x4a75ae5c

Circles and ellipses

data Circle Source #

Circles are not an instance of Transform, because e.g. shearing a circle yields an Ellipse. To transform circles, convert them to an ellipse first with toEllipse.

Constructors

Circle 

Instances

Instances details
Show Circle Source # 
Instance details

Defined in Geometry.Core

Default Circle Source #

Unit circle

Instance details

Defined in Geometry.Core

Methods

def :: Circle

NFData Circle Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Circle -> () #

Sketch Circle Source #

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Circle.svg" 200 200 $ \_ -> do
    sketch (Circle (Vec2 100 100) 90)
    stroke
:}
Generated file: size 2KB, crc32: 0x565193fd
Instance details

Defined in Draw

Methods

sketch :: Circle -> Render () Source #

Plotting Circle Source # 
Instance details

Defined in Draw.Plotting

Methods

plot :: Circle -> Plot () Source #

HasBoundingBox Circle Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_circle.svg" 150 150 $ \_ -> do
    let circle = Circle (Vec2 75 75) 65
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox circle)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch circle
        C.stroke
:}
Generated file: size 2KB, crc32: 0x58f8a8af
Instance details

Defined in Geometry.Core

Eq Circle Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Circle -> Circle -> Bool #

(/=) :: Circle -> Circle -> Bool #

Ord Circle Source # 
Instance details

Defined in Geometry.Core

newtype UnsafeTransformCircle Source #

This allows applying a Transformation to a Circle, which can e.g. be useful to put circles in a transformBoundingBox operation. See the Transform instance for details.

Instances

Instances details
Transform UnsafeTransformCircle Source #

Transform the circle as much as circles allow us. In order for the new circle to have correct radius, the transformation must not contain:

  • Shears (would yield an ellipse)
  • Scaling by different amounts in x/y directions (dito) except mirroring

This instance is unsafe in the sense that it will yield a wrong result if these requirements are not met, but it can be useful to do aspect ratio preserving scales or translations of circles.

Instance details

Defined in Geometry.Core

toEllipse :: Circle -> Ellipse Source #

Embedding of Circle as a special case of an Ellipse.

newtype Ellipse Source #

An Ellipse is a Transformation applied to the unit Circle. Create them using toEllipse and by then applying Transformations to it.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/ellipses.svg" 300 300 $ \(Vec2 w h) -> do
    let center = zero
        radius = w/6*0.9
        ellipse = toEllipse (Circle center radius)
        grid i j = C.translate (fromIntegral i*w/3 + w/6) (fromIntegral j*h/3 + w/6)
    let actions =
            [ cairoScope $ do
                grid 0 0
                for_ (take 10 [0..]) $ \i -> do
                    let scaleFactor = lerpID (0,9) (0.1, 1) i
                    sketch (transform (scaleAround center scaleFactor) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 1 0
                for_ (take 10 [0..]) $ \i -> do
                    let scaleFactor = lerpID (0,9) (0.1, 1) i
                    sketch (transform (scaleAround' center scaleFactor 1) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 2 0
                for_ [0..9] $ \i -> do
                    let scaleFactor1 = lerp (0, 9) (1, 0.1) (fromIntegral i)
                        scaleFactor2 = lerp (0, 9) (0.1, 1) (fromIntegral i)
                    sketch (transform (scaleAround' center scaleFactor1 scaleFactor2) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 0 1
                for_ (take 10 [0..]) $ \i -> do
                    let scaleX = scaleAround' center (lerpID (0,9) (0.5,1) (fromIntegral i)) 1
                        scaleY = scaleAround' center 1 (lerpID (0,9) (0.1,1) (fromIntegral i))
                    sketch (transform (scaleX <> scaleY) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 1 1
                for_ (take 10 [0..]) $ \i -> do
                    let angle = deg (lerpID (0,9) (0, 90) i)
                    sketch (transform (rotateAround center angle <> scaleAround' center 1 0.5) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 2 1
                for_ (take 19 [0..]) $ \i -> do
                    let angle = deg (lerpID (0,19) (0, 180) i)
                    sketch (transform (rotateAround center angle <> scaleAround' center 1 0.5) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 0 2
                for_ (take 9 [0..]) $ \i -> do
                    let scaleFactor = lerpID (0,9) (1,0.1) i
                        angle = deg (lerpID (0,9) (90,0) i)
                    sketch (transform (rotateAround center angle <> scaleAround' center 1 scaleFactor) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 1 2
                for_ (take 9 [0..]) $ \i -> do
                    let scaleFactor = lerpID (0,9) (1,0.1) i
                        angle = deg (lerpID (0,9) (0,90) i)
                    sketch (transform (scaleAround center scaleFactor <> rotateAround center angle <> scaleAround' center 1 scaleFactor) ellipse)
                    C.stroke
            , cairoScope $ do
                grid 2 2
                for_ (take 9 [0..]) $ \i -> do
                    let BoundingBox topLeft _ = boundingBox ellipse
                        scaleFactor = lerpID (0,9) (1,0.1) i
                        angle = deg (lerpID (0,9) (0,90) i)
                    sketch (transform (rotateAround center angle <> scaleAround (0.5 *. (topLeft -. center)) scaleFactor) ellipse)
                    C.stroke
            ]
    for_ (zip [0..] actions) $ \(i, action) -> do
        setColor (mma i)
        action
:}
Generated file: size 42KB, crc32: 0xb3cabf7b

Constructors

Ellipse Transformation 

Instances

Instances details
Show Ellipse Source # 
Instance details

Defined in Geometry.Core

Default Ellipse Source #

Unit circle

Instance details

Defined in Geometry.Core

Methods

def :: Ellipse

NFData Ellipse Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Ellipse -> () #

Sketch Ellipse Source #

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Ellipse.svg" 150 100 $ \_ -> do
    C.setLineWidth 2
    sketch (G.transform (G.translate (Vec2 75 50) <> G.rotate (deg 20) <> G.scale' 1.4 0.9)
                        (toEllipse (Circle zero 45)))
    stroke
:}
Generated file: size 2KB, crc32: 0x25bae2ef
Instance details

Defined in Draw

Methods

sketch :: Ellipse -> Render () Source #

Plotting Ellipse Source #

Approximation by a number of points

Instance details

Defined in Draw.Plotting

Methods

plot :: Ellipse -> Plot () Source #

HasBoundingBox Ellipse Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_ellipse.svg" 300 300 $ \(Vec2 w h) -> do
    let radius = w/6*0.9
        ellipse = toEllipse (Circle zero radius)
        grid i j = C.translate (fromIntegral i*w/3 + w/6) (fromIntegral j*h/3 + w/6)
        paintWithBB i j geo = cairoScope $ do
            grid i j
            setColor (mma (i*3+j))
            cairoScope $ sketch geo >> C.stroke
            cairoScope $ C.setDash [1.5,3] 0 >> sketch (boundingBox geo) >> C.stroke
    paintWithBB 0 0 (transform (scale 0.9) ellipse)
    paintWithBB 1 0 (transform (scale 0.75) ellipse)
    paintWithBB 2 0 (transform (scale 0.5) ellipse)
    paintWithBB 0 1 (transform (scale' 0.5 1) ellipse)
    paintWithBB 1 1 (transform (scale' 1 0.5) ellipse)
    paintWithBB 2 1 (transform (rotate (deg 30) <> scale' 0.5 1) ellipse)
    paintWithBB 0 2 (transform (shear 0.3 0 <> scale' 0.5 1) ellipse)
    paintWithBB 1 2 (transform (shear 0 0.3 <> scale' 1 0.5) ellipse)
    paintWithBB 2 2 (transform (scale' 1 0.5 <> rotate (deg 45) <> shear 0 1 <> scale' 1 0.5) ellipse)
:}
Generated file: size 9KB, crc32: 0xcc4b0da9
Instance details

Defined in Geometry.Core

Transform Ellipse Source # 
Instance details

Defined in Geometry.Core

Angles

data Angle Source #

Newtype safety wrapper.

Angles are not Ord, since the cyclic structure is very error-prone when combined with comparisons and VectorSpace arithmetic in practice :-( Often times, using the dotProduct (measure same-direction-ness) or cross product via det (measure leftness/rightness) is a much better choice to express what you want.

For sorting a number of points by angle, use the fast pseudoAngle function.

Instances

Instances details
Show Angle Source # 
Instance details

Defined in Geometry.Core

Methods

showsPrec :: Int -> Angle -> ShowS #

show :: Angle -> String #

showList :: [Angle] -> ShowS #

NFData Angle Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Angle -> () #

VectorSpace Angle Source # 
Instance details

Defined in Geometry.Core

ChaosSource Angle Source # 
Instance details

Defined in Geometry.Chaotic

Methods

perturb :: Angle -> Int Source #

MwcChaosSource Angle Source # 
Instance details

Defined in Geometry.Chaotic

Eq Angle Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Angle -> Angle -> Bool #

(/=) :: Angle -> Angle -> Bool #

Uniform Angle Source # 
Instance details

Defined in Geometry.Core

Methods

uniformM :: StatefulGen g m => g -> m Angle

deg :: Double -> Angle Source #

Degrees-based Angle smart constructor.

getDeg :: Angle -> Double Source #

Get an angle’s value in degrees

rad :: Double -> Angle Source #

Radians-based Angle smart constructor.

getRad :: Angle -> Double Source #

Get an angle’s value in radians

normalizeAngle Source #

Arguments

:: Angle

Interval start

-> Angle

Angle to normalize

-> Angle

Angle normalized to the interval [start, start + deg 360)

Get the angle’s value, normalized to one revolution. This makes e.g. 720° mean the same as 360°, which is otherwise not true for Angles – turning twice might be something else than turning once, after all.

pseudoAngle :: Vec2 -> Double Source #

Increases monotonically with angle. Useful pretty much only to sort points by angle, but this it does particularly fast (in particular, it’s much faster than atan2).

Here is a comparison between atan2-based (blue) and pseudoAtan2/pseudoAngle-based angle of \([0\ldots 360\deg)\). Both are increasing monotonically over the entire interval, hence yield the same properties when it comes to Ord and sorting in particular.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/pseudo_angle.svg" 200 100 $ \_ -> do
    let anglesDeg = takeWhile (< 180) [-180, -175 ..]
        atan2Plot = Polyline $ do
            angleDeg <- anglesDeg
            let Vec2 x y = polar (deg angleDeg) 1
            pure (Vec2 angleDeg (atan2 y x))
        pseudoAtan2Plot = Polyline $ do
            angleDeg <- anglesDeg
            let vec = polar (deg angleDeg) 1
            pure (Vec2 angleDeg (pseudoAngle vec))
        trafo = transformBoundingBox
            [atan2Plot, pseudoAtan2Plot]
            (shrinkBoundingBox 10 [zero, Vec2 200 100])
            def {_bbFitAspect = IgnoreAspect}
    cairoScope $ do
        sketch (transform trafo atan2Plot)
        setColor (mma 0)
        C.stroke
    cairoScope $ do
        sketch (transform trafo pseudoAtan2Plot)
        setColor (mma 1)
        C.stroke
:}
Generated file: size 5KB, crc32: 0xda270ddb

Vector arithmetic

class VectorSpace v where Source #

A generic vector space. Not only classic vectors like Vec2 form a vector space, but also concepts like Angles – anything that can be added, inverted, and multiplied with a scalar.

Vector spaces come with a number of laws that you can look up on Wikipedia. The short version is: each operation behaves like addition/multiplication on normal numbers.

Minimal complete definition

(+.), (*.), ((-.) | negateV), zero

Methods

(+.) :: v -> v -> v infixl 6 Source #

Vector addition

(-.) :: v -> v -> v infixl 6 Source #

Vector subtraction

(*.) :: Double -> v -> v infixl 7 Source #

Multiplication with a scalar

(/.) :: v -> Double -> v infixl 7 Source #

Division by a scalar

zero :: v Source #

Neutral element

negateV :: v -> v Source #

Inverse element

Instances

Instances details
VectorSpace Angle Source # 
Instance details

Defined in Geometry.Core

VectorSpace Mat2 Source # 
Instance details

Defined in Geometry.Core

VectorSpace Vec2 Source # 
Instance details

Defined in Geometry.Core

VectorSpace PhaseSpace Source # 
Instance details

Defined in Physics

VectorSpace Double Source # 
Instance details

Defined in Algebra.VectorSpace

VectorSpace a => VectorSpace (NBody a) Source # 
Instance details

Defined in Physics

Methods

(+.) :: NBody a -> NBody a -> NBody a Source #

(-.) :: NBody a -> NBody a -> NBody a Source #

(*.) :: Double -> NBody a -> NBody a Source #

(/.) :: NBody a -> Double -> NBody a Source #

zero :: NBody a Source #

negateV :: NBody a -> NBody a Source #

VectorSpace b => VectorSpace (a -> b) Source # 
Instance details

Defined in Algebra.VectorSpace

Methods

(+.) :: (a -> b) -> (a -> b) -> a -> b Source #

(-.) :: (a -> b) -> (a -> b) -> a -> b Source #

(*.) :: Double -> (a -> b) -> a -> b Source #

(/.) :: (a -> b) -> Double -> a -> b Source #

zero :: a -> b Source #

negateV :: (a -> b) -> a -> b Source #

(VectorSpace v1, VectorSpace v2) => VectorSpace (v1, v2) Source # 
Instance details

Defined in Algebra.VectorSpace

Methods

(+.) :: (v1, v2) -> (v1, v2) -> (v1, v2) Source #

(-.) :: (v1, v2) -> (v1, v2) -> (v1, v2) Source #

(*.) :: Double -> (v1, v2) -> (v1, v2) Source #

(/.) :: (v1, v2) -> Double -> (v1, v2) Source #

zero :: (v1, v2) Source #

negateV :: (v1, v2) -> (v1, v2) Source #

(VectorSpace v1, VectorSpace v2, VectorSpace v3) => VectorSpace (v1, v2, v3) Source # 
Instance details

Defined in Algebra.VectorSpace

Methods

(+.) :: (v1, v2, v3) -> (v1, v2, v3) -> (v1, v2, v3) Source #

(-.) :: (v1, v2, v3) -> (v1, v2, v3) -> (v1, v2, v3) Source #

(*.) :: Double -> (v1, v2, v3) -> (v1, v2, v3) Source #

(/.) :: (v1, v2, v3) -> Double -> (v1, v2, v3) Source #

zero :: (v1, v2, v3) Source #

negateV :: (v1, v2, v3) -> (v1, v2, v3) Source #

(VectorSpace v1, VectorSpace v2, VectorSpace v3, VectorSpace v4) => VectorSpace (v1, v2, v3, v4) Source # 
Instance details

Defined in Algebra.VectorSpace

Methods

(+.) :: (v1, v2, v3, v4) -> (v1, v2, v3, v4) -> (v1, v2, v3, v4) Source #

(-.) :: (v1, v2, v3, v4) -> (v1, v2, v3, v4) -> (v1, v2, v3, v4) Source #

(*.) :: Double -> (v1, v2, v3, v4) -> (v1, v2, v3, v4) Source #

(/.) :: (v1, v2, v3, v4) -> Double -> (v1, v2, v3, v4) Source #

zero :: (v1, v2, v3, v4) Source #

negateV :: (v1, v2, v3, v4) -> (v1, v2, v3, v4) Source #

(VectorSpace v1, VectorSpace v2, VectorSpace v3, VectorSpace v4, VectorSpace v5) => VectorSpace (v1, v2, v3, v4, v5) Source # 
Instance details

Defined in Algebra.VectorSpace

Methods

(+.) :: (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) Source #

(-.) :: (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) Source #

(*.) :: Double -> (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) Source #

(/.) :: (v1, v2, v3, v4, v5) -> Double -> (v1, v2, v3, v4, v5) Source #

zero :: (v1, v2, v3, v4, v5) Source #

negateV :: (v1, v2, v3, v4, v5) -> (v1, v2, v3, v4, v5) Source #

vsum :: VectorSpace a => [a] -> a Source #

Transformations

class Transform geo where Source #

Transform geometry using an affine transformation.

This allows for a multitude of common transformations, such as translation (translate), rotation (rotate) or scaling (scale).

Simple transformations can be combined to yield more complex operations, such as rotating around a point, which can be achieved by moving the center of rotation to the origin, rotating, and then rotating back:

rotateAround pivot angle = translate pivot <> rotate angle <> inverse (translate pivot)

Methods

transform :: Transformation -> geo -> geo Source #

Instances

Instances details
Transform Bezier Source # 
Instance details

Defined in Geometry.Bezier

Transform BoundingBox Source # 
Instance details

Defined in Geometry.Core

Transform Ellipse Source # 
Instance details

Defined in Geometry.Core

Transform Line Source # 
Instance details

Defined in Geometry.Core

Transform Polygon Source # 
Instance details

Defined in Geometry.Core

Transform Polyline Source # 
Instance details

Defined in Geometry.Core

Transform Transformation Source # 
Instance details

Defined in Geometry.Core

Transform UnsafeTransformCircle Source #

Transform the circle as much as circles allow us. In order for the new circle to have correct radius, the transformation must not contain:

  • Shears (would yield an ellipse)
  • Scaling by different amounts in x/y directions (dito) except mirroring

This instance is unsafe in the sense that it will yield a wrong result if these requirements are not met, but it can be useful to do aspect ratio preserving scales or translations of circles.

Instance details

Defined in Geometry.Core

Transform Vec2 Source # 
Instance details

Defined in Geometry.Core

Transform Tile Source # 
Instance details

Defined in Geometry.Processes.Penrose

(Ord a, Transform a) => Transform (Set a) Source #

Points mapped to the same point will unify to a single entry

Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> Set a -> Set a Source #

Transform a => Transform (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Transform (NoTransform a) Source #

Don’t transform the contents.

Instance details

Defined in Geometry.Core

Transform a => Transform (Vector a) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> Vector a -> Vector a Source #

Transform a => Transform [a] Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> [a] -> [a] Source #

(Transform a, Transform b) => Transform (Either a b) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> Either a b -> Either a b Source #

Transform a => Transform (Map k a) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> Map k a -> Map k a Source #

Transform b => Transform (a -> b) Source #

Transform the result of a function.

moveRight :: Vec2 -> Vec2
moveRight (Vec2 x y) = Vec2 (x+1) y

moveRightThenRotate :: Vec2 -> Vec2
moveRightThenRotate = transform (rotate (deg 90)) moveRight
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> (a -> b) -> a -> b Source #

(Transform a, Transform b) => Transform (a, b) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> (a, b) -> (a, b) Source #

(Transform a, Transform b, Transform c) => Transform (a, b, c) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> (a, b, c) -> (a, b, c) Source #

(Transform a, Transform b, Transform c, Transform d) => Transform (a, b, c, d) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> (a, b, c, d) -> (a, b, c, d) Source #

(Transform a, Transform b, Transform c, Transform d, Transform e) => Transform (a, b, c, d, e) Source # 
Instance details

Defined in Geometry.Core

Methods

transform :: Transformation -> (a, b, c, d, e) -> (a, b, c, d, e) Source #

data Transformation Source #

Affine transformation. Typically these are not written using the constructor directly, but by combining functions such as translate or rotateAround using <>.

\[ \begin{pmatrix}\mathbf{x'}\\1\end{pmatrix} = \begin{pmatrix} A \mathbf x + \mathbf b\\1\end{pmatrix} = \left(\begin{array}{c|c} A & \mathbf b \\ \hline 0 & 1\end{array}\right) \begin{pmatrix}\mathbf x\\ 1\end{pmatrix} \]

Constructors

Transformation !Mat2 !Vec2
transformation (Mat2 a11 a12
                     a21 a22)
               (Vec2 b1 b2)

\(= \left(\begin{array}{cc|c} a_{11} & a_{12} & b_1 \\ a_{21} & a_{22} & b_2 \\ \hline 0 & 0 & 1\end{array}\right)\)

Instances

Instances details
Monoid Transformation Source # 
Instance details

Defined in Geometry.Core

Semigroup Transformation Source #

The order transformations are applied in function order:

transform (scale s <> translate p)
==
transform (scale s) . transform (translate p)

In other words, this first translates its argument, and then scales. Note that Cairo does its Canvas transformations just the other way round, since in Cairo you do not move the geometry, but the coordinate system. If you wrap a transformation in inverse, you get the Cairo behavior.

Instance details

Defined in Geometry.Core

Show Transformation Source # 
Instance details

Defined in Geometry.Core

NFData Transformation Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Transformation -> () #

Group Transformation Source # 
Instance details

Defined in Geometry.Core

Sketch Transformation Source #

Draw a \(100\times 100\) square with its corner at zero and transformed with the Transformation, sometimes useful for debugging.

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_Transformation.svg" 300 200 $ \_ -> do
    C.setLineWidth 2
    setColor (mma 0) >> sketch (G.translate (Vec2 20 20)) >> stroke
    setColor (mma 1) >> sketch (G.translate (Vec2 110 50) <> G.rotate (deg 30)) >> stroke
    setColor (mma 2) >> sketch (G.shear 0.5 0.2 <> G.translate (Vec2 140 0)) >> stroke
:}
Generated file: size 4KB, crc32: 0x1f4ae5da
Instance details

Defined in Draw

Methods

sketch :: Transformation -> Render () Source #

Transform Transformation Source # 
Instance details

Defined in Geometry.Core

Eq Transformation Source # 
Instance details

Defined in Geometry.Core

Ord Transformation Source # 
Instance details

Defined in Geometry.Core

newtype NoTransform a Source #

This type simply wraps its contents, and makes transform do nothing. It’s a very useful type when you want to e.g. resize the whole geometry given to your rendering function, but it contains some non-geometrical render data, like a timestamp for each shape.

Constructors

NoTransform a 

Instances

Instances details
Monoid a => Monoid (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Semigroup a => Semigroup (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Bounded a => Bounded (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Enum a => Enum (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Read a => Read (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Show a => Show (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

NFData a => NFData (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: NoTransform a -> () #

HasBoundingBox a => HasBoundingBox (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Transform (NoTransform a) Source #

Don’t transform the contents.

Instance details

Defined in Geometry.Core

Eq a => Eq (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

Ord a => Ord (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

translate :: Vec2 -> Transformation Source #

Translate the argument by an offset given by the vector. translate (Vec2 0 0) = mempty.

\[ \text{translate}\begin{pmatrix}\Delta_x\\\Delta_y\end{pmatrix} = \left(\begin{array}{cc|c} 1 & 0 & \Delta_x \\ 0 & 1 & \Delta_y \\ \hline 0 & 0 & 1\end{array}\right) \]

This effectively adds the Vec2 to all contained Vec2s in the target.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/translate.svg" 100 50 $ \_ -> do
    let point = Vec2 20 20
        offset = Vec2 70 20
        point' = transform (translate offset) point
    sketch (Circle point 5)
    sketch (Circle point' 5)
    C.fill
    setColor (mma 1)
    sketch (Arrow (Line point point') def) >> C.stroke
:}
Generated file: size 2KB, crc32: 0x5f8c6969

rotate :: Angle -> Transformation Source #

Rotate around zero in mathematically positive direction (counter-clockwise). rotate (rad 0) = mempty.

\[ \text{rotate}(\alpha) = \left(\begin{array}{cc|c} \cos(\alpha) & -\sin(\alpha) & 0 \\ \sin(\alpha) & \cos(\alpha) & 0 \\ \hline 0 & 0 & 1\end{array}\right) \]

To rotate around a different point, use rotateAround.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/rotate.svg" 100 70 $ \_ -> do
    let point = Vec2 90 10
        angle = deg 30
        point' = transform (rotate angle) point
    cairoScope $ do
        sketch (Circle point 5, Circle point' 5)
        C.fill
    setColor (mma 1)
    let line = Line zero point
        line' = Line zero point'
    cairoScope $ do
        C.setDash [1,1] 0
        sketch (line, line')
        C.stroke
    cairoScope $ do
        let angle = angleOfLine line
            angle' = angleOfLine line'
        C.arc 0 0 (lineLength line) (getRad angle) (getRad angle')
        sketch (Arrow (transform (rotateAround point' (deg 15)) (Line point point')) def{_arrowDrawBody=False})
        C.stroke
:}
Generated file: size 2KB, crc32: 0x980f5f

rotateAround :: Vec2 -> Angle -> Transformation Source #

Rotate around a point.

scale :: Double -> Transformation Source #

Scale the geometry relative to zero, maintaining aspect ratio.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/scale.svg" 100 100 $ \_ -> do
    let square = Polygon [Vec2 10 10, Vec2 10 45, Vec2 45 45, Vec2 45 10]
        square' = transform (scale 2) square
    sketch square
    C.stroke
    setColor (mma 1)
    sketch square'
    C.stroke
:}
Generated file: size 2KB, crc32: 0x9126ef6

scale' :: Double -> Double -> Transformation Source #

Scale the geometry with adjustable aspect ratio. scale' 1 1 = mempty.

\[ \text{scale'}(s_x,s_y) = \left(\begin{array}{cc|c} s_x & 0 & 0 \\ 0 & s_y & 0 \\ \hline 0 & 0 & 1\end{array}\right) \]

While being more general and mathematically more natural than scale, this function is used less in practice, hence it gets the prime in the name.

scaleAround :: Vec2 -> Double -> Transformation Source #

Scale the geometry relative to a point, maintaining aspect ratio.

scaleAround' :: Vec2 -> Double -> Double -> Transformation Source #

Scale the geometry relative to a point, with adjustable aspect ratio.

While being more general and mathematically more natural, this function is used less in practice, hence it gets the prime in the name.

mirrorAlong :: Line -> Transformation Source #

Mirror the geometry along a line.

This function is called mirrorAlong and not mirror since the latter makes a very good name for arguments of this function.

mirrorXCoords :: Transformation Source #

Invert all X coordinates.

NB: if it was called mirrorX it wouldn’t be clear whether it mirrors the X coordinates, or along the X axis, which would mirror the Y coordinates. The longer name makes it clearer.

mirrorYCoords :: Transformation Source #

Invert all Y coordinates.

shear :: Double -> Double -> Transformation Source #

Shear with a factor along x/y axis relative to zero. shear 0 0 = mempty.

\[ \text{shear}(p,q) = \left(\begin{array}{cc|c} 1 & p & 0 \\ q & 1 & 0 \\ \hline 0 & 0 & 1\end{array}\right) \]

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/shear.svg" 100 100 $ \_ -> do
    let square = Polygon [Vec2 10 10, Vec2 10 80, Vec2 50 80, Vec2 50 10]
        square' = transform (shear 0.5 0.1) square
    sketch square
    C.stroke
    setColor (mma 1)
    sketch square'
    C.stroke
:}
Generated file: size 2KB, crc32: 0xfd1224e4

decomposeTransformation Source #

Arguments

:: Transformation 
-> (Vec2, (Double, Double), Double, Angle)

\(\Delta\mathbf v, (\text{scale}_x,\text{scale}_y), \text{shear}_y, \varphi\)

Decompose an affine transformation into scale, shear, rotation and translation parts. This composition is not unique, since scale, shear and rotating can be done in different orders.

Our choice of decomposition is:

\[ \left(\begin{array}{cc|c} a_{11} & a_{12} & b_1 \\ a_{21} & a_{22} & b_2 \\ \hline & & 1\end{array}\right) = \underbrace{\left(\begin{array}{cc|c} 1 & & \Delta_x \\ & 1 & \Delta_y \\ \hline & & 1\end{array}\right)} _{\text{translate}(\Delta_x, \Delta_y)} \underbrace{\left(\begin{array}{cc|c} s_x & & \\ & s_y & \\ \hline & & 1\end{array}\right)} _{\text{scale}'(s_x,s_y)} \underbrace{\left(\begin{array}{cc|c} 1 & & \\ \sigma_y & 1 & \\ \hline & & 1\end{array}\right)} _{\text{shear}(0, \sigma_y)} \underbrace{\left(\begin{array}{cc|c} \cos(\varphi) & -\sin(\varphi) & \\ \sin(\varphi) & \cos(\varphi) & \\ \hline & & 1\end{array}\right)} _{\text{rotatate}(\varphi)} \]

Bounding Box

class HasBoundingBox a where Source #

Anything we can paint has a bounding box. Knowing it is useful to e.g. rescale the geometry to fit into the canvas or for collision detection.

Instances

Instances details
HasBoundingBox Canvas Source # 
Instance details

Defined in Draw.Plotting.CmdArgs

HasBoundingBox Bezier Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Bezier/bounding_box_bezier.svg" 150 100 $ \_ -> do
    let curve = let curveRaw = transform (rotate (deg (-30))) (Bezier (Vec2 0 0) (Vec2 1 5) (Vec2 2.5 (-1)) (Vec2 3 3))
                    fitToBox = transform (transformBoundingBox curveRaw (shrinkBoundingBox 10 [zero, Vec2 150 100]) (TransformBBSettings FitWidthHeight IgnoreAspect FitAlignCenter))
                in fitToBox curveRaw
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox curve)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch curve
        C.stroke
:}
Generated file: size 2KB, crc32: 0x73983106
Instance details

Defined in Geometry.Bezier

HasBoundingBox BoundingBox Source #

This is simply id, so a bounding box that doesn’t satisfy the min/max invariant will remain incorrect.

Instance details

Defined in Geometry.Core

HasBoundingBox Circle Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_circle.svg" 150 150 $ \_ -> do
    let circle = Circle (Vec2 75 75) 65
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox circle)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch circle
        C.stroke
:}
Generated file: size 2KB, crc32: 0x58f8a8af
Instance details

Defined in Geometry.Core

HasBoundingBox Ellipse Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_ellipse.svg" 300 300 $ \(Vec2 w h) -> do
    let radius = w/6*0.9
        ellipse = toEllipse (Circle zero radius)
        grid i j = C.translate (fromIntegral i*w/3 + w/6) (fromIntegral j*h/3 + w/6)
        paintWithBB i j geo = cairoScope $ do
            grid i j
            setColor (mma (i*3+j))
            cairoScope $ sketch geo >> C.stroke
            cairoScope $ C.setDash [1.5,3] 0 >> sketch (boundingBox geo) >> C.stroke
    paintWithBB 0 0 (transform (scale 0.9) ellipse)
    paintWithBB 1 0 (transform (scale 0.75) ellipse)
    paintWithBB 2 0 (transform (scale 0.5) ellipse)
    paintWithBB 0 1 (transform (scale' 0.5 1) ellipse)
    paintWithBB 1 1 (transform (scale' 1 0.5) ellipse)
    paintWithBB 2 1 (transform (rotate (deg 30) <> scale' 0.5 1) ellipse)
    paintWithBB 0 2 (transform (shear 0.3 0 <> scale' 0.5 1) ellipse)
    paintWithBB 1 2 (transform (shear 0 0.3 <> scale' 1 0.5) ellipse)
    paintWithBB 2 2 (transform (scale' 1 0.5 <> rotate (deg 45) <> shear 0 1 <> scale' 1 0.5) ellipse)
:}
Generated file: size 9KB, crc32: 0xcc4b0da9
Instance details

Defined in Geometry.Core

HasBoundingBox Line Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_line.svg" 150 100 $ \_ -> do
    let line = Line (Vec2 10 10) (Vec2 140 90)
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox line)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch line
        C.stroke
:}
Generated file: size 2KB, crc32: 0xd5f4f645
Instance details

Defined in Geometry.Core

HasBoundingBox Polygon Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox Polyline Source #

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/bounding_box_polyline.svg" 150 100 $ \_ -> do
    let polyline = Polyline [Vec2 10 10, Vec2 90 90, Vec2 120 10, Vec2 140 50]
    cairoScope $ do
        setColor (mma 1)
        C.setDash [1.5,3] 0
        sketch (boundingBox polyline)
        C.stroke
    cairoScope $ do
        C.setLineWidth 2
        sketch polyline
        C.stroke
:}
Generated file: size 2KB, crc32: 0xd45fc8b5
Instance details

Defined in Geometry.Core

HasBoundingBox Vec2 Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox SvgElement Source # 
Instance details

Defined in Geometry.SvgParser

HasBoundingBox a => HasBoundingBox (Set a) Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox (NoBoundingBox a) Source #

Contents are ignored, reporting an empty bounding box.

Instance details

Defined in Geometry.Core

HasBoundingBox a => HasBoundingBox (NoTransform a) Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox a => HasBoundingBox (Vector a) Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: Vector a -> BoundingBox Source #

HasBoundingBox a => HasBoundingBox (Maybe a) Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox a => HasBoundingBox [a] Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: [a] -> BoundingBox Source #

(HasBoundingBox a, HasBoundingBox b) => HasBoundingBox (Either a b) Source # 
Instance details

Defined in Geometry.Core

HasBoundingBox a => HasBoundingBox (Map k a) Source # 
Instance details

Defined in Geometry.Core

(HasBoundingBox a, HasBoundingBox b) => HasBoundingBox (a, b) Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: (a, b) -> BoundingBox Source #

(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c) => HasBoundingBox (a, b, c) Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: (a, b, c) -> BoundingBox Source #

(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c, HasBoundingBox d) => HasBoundingBox (a, b, c, d) Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: (a, b, c, d) -> BoundingBox Source #

(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c, HasBoundingBox d, HasBoundingBox e) => HasBoundingBox (a, b, c, d, e) Source # 
Instance details

Defined in Geometry.Core

Methods

boundingBox :: (a, b, c, d, e) -> BoundingBox Source #

data BoundingBox Source #

The bounding box, with the minimum and maximum vectors.

In geometrical terms, the bounding box is a rectangle spanned by the bottom-left (minimum) and top-right (maximum) points, so that everything is inside the rectangle.

Invariant! Make sure the first argument is smaller than the second when using the constructor directly! Or better yet, don’t use the constructor and create bounding boxes via the provided instances; for a rectangle, simply use boundingBox (a,b) instead of BoundingBox a b.

Constructors

BoundingBox !Vec2 !Vec2 

Instances

Instances details
Monoid BoundingBox Source #

A bounding box with the minimum at (plus!) infinity and maximum at (minus!) infinity acts as a neutral element. This is mostly useful so we can make potentiallly empty data structures such as [a] and Maybe a instances of HasBoundingBox.

Instance details

Defined in Geometry.Core

Semigroup BoundingBox Source # 
Instance details

Defined in Geometry.Core

Show BoundingBox Source # 
Instance details

Defined in Geometry.Core

NFData BoundingBox Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: BoundingBox -> () #

Sketch BoundingBox Source #

Sketches a rectangle with a diagonal cross through it. Useful for debugging.

(image code)

Expand
>>> :{
haddockRender "Draw/instance_Sketch_BoundingBox.svg" 100 100 $ \_ -> do
    let geometry = [Circle (Vec2 30 30) 25, Circle (Vec2 60 60) 35]
    for_ geometry $ \x -> cairoScope (sketch x >> setColor (mma 1) >> setDash [4,6] 0 >> stroke)
    sketch (boundingBox geometry)
    stroke
:}
Generated file: size 3KB, crc32: 0xfed2c044
Instance details

Defined in Draw

Methods

sketch :: BoundingBox -> Render () Source #

Plotting BoundingBox Source #

Trace the bounding box without actually drawing anything to estimate result size

Instance details

Defined in Draw.Plotting

Methods

plot :: BoundingBox -> Plot () Source #

ChaosSource BoundingBox Source # 
Instance details

Defined in Geometry.Chaotic

MwcChaosSource BoundingBox Source # 
Instance details

Defined in Geometry.Chaotic

HasBoundingBox BoundingBox Source #

This is simply id, so a bounding box that doesn’t satisfy the min/max invariant will remain incorrect.

Instance details

Defined in Geometry.Core

Transform BoundingBox Source # 
Instance details

Defined in Geometry.Core

Eq BoundingBox Source # 
Instance details

Defined in Geometry.Core

Ord BoundingBox Source # 
Instance details

Defined in Geometry.Core

newtype NoBoundingBox a Source #

This type simply wraps its contents, but reports its bounding box as mempty. It’s a very useful type when you want to e.g. resize the whole geometry given to your rendering function, but it contains some non-geometrical render data, like a timestamp for each shape.

Constructors

NoBoundingBox a 

Instances

Instances details
Monoid a => Monoid (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Semigroup a => Semigroup (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Bounded a => Bounded (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Enum a => Enum (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Read a => Read (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Show a => Show (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

NFData a => NFData (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: NoBoundingBox a -> () #

HasBoundingBox (NoBoundingBox a) Source #

Contents are ignored, reporting an empty bounding box.

Instance details

Defined in Geometry.Core

Transform a => Transform (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Eq a => Eq (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

Ord a => Ord (NoBoundingBox a) Source # 
Instance details

Defined in Geometry.Core

overlappingBoundingBoxes :: (HasBoundingBox a, HasBoundingBox b) => a -> b -> Bool Source #

Do the bounding boxes of two objects overlap?

transformBoundingBox Source #

Arguments

:: (HasBoundingBox source, HasBoundingBox target) 
=> source

e.g. drawing coordinate system

-> target

e.g. Cairo canvas

-> TransformBBSettings 
-> Transformation 

Generate a transformation that transforms the bounding box of one object to match the other’s. Canonical use case: transform any part of your graphic to fill the Cairo canvas.

data FitDimension Source #

Constructors

FitWidthHeight

Fit both width and height

FitWidth

Fit width, ignoring what happens to the height (allow y stretching/compression)

FitHeight

Fit height, ignoring what happens to the width (allow x stretching/compression)

FitNone

Don't fit dimensions, only align

data FitAspect Source #

Constructors

MaintainAspect

Maintain width:height aspect ratio

IgnoreAspect

Ignore aspect ratio

Instances

Instances details
Show FitAspect Source # 
Instance details

Defined in Geometry.Core

Eq FitAspect Source # 
Instance details

Defined in Geometry.Core

Ord FitAspect Source # 
Instance details

Defined in Geometry.Core

data FitAlign Source #

Constructors

FitAlignCenter

Align the centers of the results

FitAlignTopLeft

Align the top left of the results

FitAlignTopRight

Align the top right of the results

FitAlignBottomLeft

Align the bottom left of the results

FitAlignBottomRight

Align the bottom right of the results

Instances

Instances details
Show FitAlign Source # 
Instance details

Defined in Geometry.Core

Eq FitAlign Source # 
Instance details

Defined in Geometry.Core

Ord FitAlign Source # 
Instance details

Defined in Geometry.Core

boundingBoxPolygon :: HasBoundingBox object => object -> Polygon Source #

The rectangle representing a BoundingBox, with positive orientation.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/boundingBoxPolygon.svg" 200 200 $ \(Vec2 w h) -> do
    points <- C.liftIO $ MWC.withRng [] $ \gen -> do
        let region = shrinkBoundingBox 20 [zero, Vec2 w h]
        poissonDisc gen region 15 5
    for_ points $ \p -> sketch (Circle p 3) >> C.fill
    setColor (mma 1)
    sketch (boundingBoxPolygon points) >> C.setLineWidth 3 >> C.stroke
:}
Generated file: size 25KB, crc32: 0x40902165

insideBoundingBox :: (HasBoundingBox thing, HasBoundingBox bigObject) => thing -> bigObject -> Bool Source #

Is the argument’s bounding box fully contained in another’s bounding box?

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/insideBoundingBox.svg" 200 200 $ \(Vec2 w h) -> do
    let parentBB = shrinkBoundingBox 50 [zero, Vec2 w h]
        paintCheck :: (HasBoundingBox object, Sketch object) => object -> C.Render ()
        paintCheck object = do
            when (not (object `insideBoundingBox` parentBB)) $ grouped (C.paintWithAlpha 0.2) $ do
                setColor (mma 3)
                sketch (boundingBox object)
                C.stroke
            sketch object
            C.stroke
    cairoScope $ C.setLineWidth 3 >> setColor (mma 3) >> sketch (boundingBoxPolygon parentBB) >> C.stroke
    setColor (mma 0) >> paintCheck (Circle (Vec2 110 60) 20)
    setColor (mma 1) >> paintCheck (Circle (Vec2 80 110) 15)
    setColor (mma 2) >> paintCheck (Line (Vec2 20 40) (Vec2 60 90))
    setColor (mma 4) >> paintCheck (transform (translate (Vec2 130 130) <> scale 30) (Geometry.Shapes.regularPolygon 5))
:}
Generated file: size 5KB, crc32: 0x8c351375

boundingBoxCenter :: HasBoundingBox a => a -> Vec2 Source #

Center/mean/centroid of a bounding box.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/boundingBoxCenter.svg" 200 200 $ \(Vec2 w h) -> do
    points <- C.liftIO $ MWC.withRng [] $ \gen -> do
        let region = shrinkBoundingBox 20 [zero, Vec2 w h]
        poissonDisc gen region 15 5
    let pointsBB = boundingBox points
    for_ points $ \p -> sketch (Circle p 3) >> C.fill
    setColor (mma 1)
    sketch (boundingBoxPolygon pointsBB)
    sketch (Cross (boundingBoxCenter pointsBB) 10)
    sketch (Circle (boundingBoxCenter pointsBB) 10)
    C.stroke
:}
Generated file: size 25KB, crc32: 0x31972ee1

boundingBoxIntersection Source #

Arguments

:: (HasBoundingBox a, HasBoundingBox b) 
=> a 
-> b 
-> Maybe BoundingBox

Nothing if the input boxes don’t have finite overlap.

Bounding box of the intersection of two bounding boxes. This is the intersection analogon to <> representing union.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/boundingBoxIntersection.svg" 200 200 $ \(Vec2 w h) -> do
    (p1s, p2s) <- C.liftIO $ MWC.withRng [] $ \gen -> do
        let region1 = shrinkBoundingBox 20 [zero, Vec2 150 150]
        p1s <- poissonDisc gen region1 15 5
        let region2 = shrinkBoundingBox 20 [Vec2 50 50, Vec2 200 200]
        p2s <- poissonDisc gen region2 15 5
        pure (p1s, p2s)
    for_ p1s $ \p -> sketch (Circle p 3) >> setColor (mma 0) >> C.fill
    for_ p2s $ \p -> sketch (Circle p 3) >> setColor (mma 1) >> C.fill
    sketch (fmap boundingBoxPolygon (boundingBoxIntersection p1s p2s)) >> setColor (mma 3) >> C.stroke
:}
Generated file: size 25KB, crc32: 0x72cfba9f

boundingBoxSize :: HasBoundingBox a => a -> (Double, Double) Source #

Width and height of a BoundingBox.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/boundingBoxSize.svg" 300 200 $ \wh -> do
    let bb = shrinkBoundingBox 20 [zero, wh]
        (bbWidth, bbHeight) = boundingBoxSize bb
    cairoScope $ do
        setColor (mma 0)
        sketch (boundingBoxPolygon bb)
        C.stroke
    let BoundingBox (Vec2 minX minY) (Vec2 maxX maxY) = shrinkBoundingBox 7 bb
        arrowBodyX = resizeLineSymmetric (\l -> l-5) (Line (Vec2 minX minY) (Vec2 maxX minY))
        arrowBodyY = resizeLineSymmetric (\l -> l-5) (Line (Vec2 minX maxY) (Vec2 minX minY))
    let style = def { _arrowheadSize = 7 }
    cairoScope $ do
        setColor (mma 1)
        cairoScope $ do
            sketch (Arrow arrowBodyX style)
            sketch (Arrow (lineReverse arrowBodyX) style {_arrowDrawBody = False})
            C.stroke
        cairoScope $ do
            let Line start end = arrowBodyX
                Line _ textGoesHere = resizeLine (const 5) (perpendicularBisector arrowBodyX)
            let textOpts = PlotTextOptions
                    { _textStartingPoint = textGoesHere
                    , _textHeight = 10
                    , _textHAlign = HCenter
                    , _textVAlign = VBottom
                    }
            for_ (plotText textOpts (printf "width: %.f px" bbWidth)) sketch
            C.stroke
    cairoScope $ do
        setColor (mma 3)
        cairoScope $ do
            sketch (Arrow arrowBodyY style)
            sketch (Arrow (lineReverse arrowBodyY) style {_arrowDrawBody = False})
            C.stroke
        cairoScope $ do
            let Line start end = arrowBodyY
                Line _ textGoesHere = resizeLine (const 5) (perpendicularBisector arrowBodyY)
            let textOpts = PlotTextOptions
                    { _textStartingPoint = textGoesHere
                    , _textHeight = 10
                    , _textHAlign = HLeft
                    , _textVAlign = VCenter
                    }
            for_ (plotText textOpts (printf "height: %.f px" bbHeight)) sketch
            C.stroke
:}
Generated file: size 9KB, crc32: 0x1ff78b56

growBoundingBox Source #

Arguments

:: HasBoundingBox boundingBox 
=> Double

Amount \(x\) to move each side. Note that e.g. the total width will increase by \(2\times x\).

-> boundingBox 
-> BoundingBox 

Grow the bounding box by moving all its bounds outwards by a specified amount. Useful to introduce margins. Negative values shrink instead; shrinkBoundingBox is a convenience wrapper for this case.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/growBoundingBox.svg" 200 200 $ \(Vec2 w h) -> do
    let bb = shrinkBoundingBox 40 [zero, Vec2 w h]
    for_ [0,5..25] $ \amount -> cairoScope $ do
        when (amount == 0) (C.setLineWidth 3)
        sketch (boundingBoxPolygon (growBoundingBox (fromIntegral amount) bb))
        setColor (icefire (Numerics.Interpolation.lerp (0,25) (0.5, 1) (fromIntegral amount)))
        C.stroke
:}
Generated file: size 4KB, crc32: 0xedcda2c4

shrinkBoundingBox :: HasBoundingBox boundingBox => Double -> boundingBox -> BoundingBox Source #

Convenience function for growBoundingBox with a negative amount.

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/shrinkBoundingBox.svg" 200 200 $ \(Vec2 w h) -> do
    let bb = shrinkBoundingBox 40 [zero, Vec2 w h]
    for_ [0,5..25] $ \amount -> cairoScope $ do
        when (amount == 0) (C.setLineWidth 3)
        sketch (boundingBoxPolygon (shrinkBoundingBox (fromIntegral amount) bb))
        setColor (icefire (Numerics.Interpolation.lerp (0,25) (0.5, 0) (fromIntegral amount)))
        C.stroke
:}
Generated file: size 4KB, crc32: 0xe0236688

Matrices

data Mat2 Source #

\(2\times2\) matrix.

Constructors

Mat2 !Double !Double !Double !Double

Mat2 a11 a12 a21 a22 \(= \begin{pmatrix}a_{11} & a_{12}\\ a_{21} & a_{22}\end{pmatrix}\)

Instances

Instances details
Monoid Mat2 Source #

Multiplicative monoid.

Instance details

Defined in Geometry.Core

Methods

mempty :: Mat2 #

mappend :: Mat2 -> Mat2 -> Mat2 #

mconcat :: [Mat2] -> Mat2 #

Semigroup Mat2 Source #

Multiplicative semigroup.

Instance details

Defined in Geometry.Core

Methods

(<>) :: Mat2 -> Mat2 -> Mat2 #

sconcat :: NonEmpty Mat2 -> Mat2 #

stimes :: Integral b => b -> Mat2 -> Mat2 #

Show Mat2 Source # 
Instance details

Defined in Geometry.Core

Methods

showsPrec :: Int -> Mat2 -> ShowS #

show :: Mat2 -> String #

showList :: [Mat2] -> ShowS #

NFData Mat2 Source # 
Instance details

Defined in Geometry.Core

Methods

rnf :: Mat2 -> () #

Group Mat2 Source #

Multiplicative group.

Instance details

Defined in Geometry.Core

Methods

inverse :: Mat2 -> Mat2 Source #

VectorSpace Mat2 Source # 
Instance details

Defined in Geometry.Core

Eq Mat2 Source # 
Instance details

Defined in Geometry.Core

Methods

(==) :: Mat2 -> Mat2 -> Bool #

(/=) :: Mat2 -> Mat2 -> Bool #

Ord Mat2 Source # 
Instance details

Defined in Geometry.Core

Methods

compare :: Mat2 -> Mat2 -> Ordering #

(<) :: Mat2 -> Mat2 -> Bool #

(<=) :: Mat2 -> Mat2 -> Bool #

(>) :: Mat2 -> Mat2 -> Bool #

(>=) :: Mat2 -> Mat2 -> Bool #

max :: Mat2 -> Mat2 -> Mat2 #

min :: Mat2 -> Mat2 -> Mat2 #

det :: Mat2 -> Double Source #

Determinant a matrix.

mulMV :: Mat2 -> Vec2 -> Vec2 Source #

Multiply a matrix \(A\) with a (column) vector \(\mathbf b\).

\[ \sum_i\mathbf e_i c_i = \sum_{ij}\mathbf e_i a_{ij} b_j \quad\Leftrightarrow\quad \begin{pmatrix}c_1\\c_2\end{pmatrix} = \begin{pmatrix}a_{11}&a_{12}\\ a_{21}&a_{22}\end{pmatrix} \begin{pmatrix}b_1\\ b_2\end{pmatrix} = \begin{pmatrix} a_{11}b_1+a_{12}b_2 \\ a_{21}b_1+a_{22}b_2 \end{pmatrix} \]

mulVTM :: Vec2 -> Mat2 -> Vec2 Source #

Multiply a (row) vector \(\mathbf a^\top\) with a matrix \(A\).

\[ \sum_i\mathbf e_i c_i = \sum_{ij} b_i a_{ij} \mathbf e_j \quad\Leftrightarrow\quad \begin{pmatrix}c_1&c_2\end{pmatrix} = \begin{pmatrix}b_1&b_2\end{pmatrix} \begin{pmatrix}a_{11}&a_{12}\\ a_{21}&a_{22}\end{pmatrix} = \begin{pmatrix} b_1a_{11}+b_2a_{21} & b_1a_{12}+b_2a_{22} \end{pmatrix} \]

Useful stuff

vectorOf :: Line -> Vec2 Source #

Directional vector of a line, i.e. the vector pointing from start to end. The norm of the vector is the length of the line. Use direction if you need a result of length 1.

cross :: Vec2 -> Vec2 -> Double Source #

Two-dimensional cross product.

This is useful to calculate the (signed) area of the parallelogram spanned by two vectors, or to check whether a vector is to the left or right of another vector.

>>> cross (Vec2 1 0) (Vec2 1 0) -- Collinear
0.0
>>> cross (Vec2 1 0) (Vec2 1 0.1) -- 2nd vec is in positive (counter-clockwise) direction
0.1
>>> cross (Vec2 1 0) (Vec2 1 (-0.1)) -- 2nd vec is in negative (clockwise) direction
-0.1

(image code)

Expand
>>> :{
haddockRender "Geometry/Core/cross_product_leftness_rightness.svg" 200 200 $ \_ -> do
    let line = Line (Vec2 10 10) (Vec2 170 170)
    points <- C.liftIO $ MWC.withRng [] $ \gen ->
            poissonDisc gen (shrinkBoundingBox 10 [Vec2 50 50, Vec2 200 200]) 10 10
    let MinMax lo hi = foldMap (\x -> MinMax x x) $ do
            end <- points
            let Line start _ = line
            pure (cross (end -. start) (vectorOf line))
    for_ points $ \end -> grouped (C.paintWithAlpha 0.7) $ do
        let Line start _ = line
        sketch (Line start end)
        setColor (icefire (lerp (min lo (-hi),max (-lo) hi) (0,1) (cross (end -. start) (vectorOf line))))
        C.stroke
        sketch (Circle end 2)
        C.fill
    cairoScope $ do
        C.setLineWidth 4
        setColor black
        sketch line
        C.stroke
        let Line start end = line
        sketch (Circle start 4)
        sketch (Circle end 4)
        C.fill
:}
Generated file: size 118KB, crc32: 0x75a4133f

direction :: Line -> Vec2 Source #

Direction vector of a line.

class Monoid a => Group a where Source #

Methods

inverse :: a -> a Source #

Instances

Instances details
Group Mat2 Source #

Multiplicative group.

Instance details

Defined in Geometry.Core

Methods

inverse :: Mat2 -> Mat2 Source #

Group Transformation Source # 
Instance details

Defined in Geometry.Core