Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Synopsis
- data Vec2 = Vec2 !Double !Double
- dotProduct :: Vec2 -> Vec2 -> Double
- norm :: Vec2 -> Double
- normSquare :: Vec2 -> Double
- polar :: Angle -> Double -> Vec2
- data Line = Line !Vec2 !Vec2
- angleOfLine :: Line -> Angle
- angleBetween :: Line -> Line -> Angle
- angledLine :: Vec2 -> Angle -> Double -> Line
- lineLength :: Line -> Double
- moveAlongLine :: Line -> Double -> Vec2
- resizeLine :: (Double -> Double) -> Line -> Line
- resizeLineSymmetric :: (Double -> Double) -> Line -> Line
- centerLine :: Line -> Line
- normalizeLine :: Line -> Line
- lineReverse :: Line -> Line
- perpendicularBisector :: Line -> Line
- perpendicularLineThrough :: Vec2 -> Line -> Line
- distanceFromLine :: Vec2 -> Line -> Double
- intersectInfiniteLines :: Line -> Line -> Vec2
- data LLIntersection
- intersectionLL :: Line -> Line -> LLIntersection
- intersectionPoint :: LLIntersection -> Maybe Vec2
- subdivideLine :: Int -> Line -> [Vec2]
- subdivideLineByLength :: Double -> Line -> [Vec2]
- reflection :: Line -> Line -> Maybe (Line, Vec2, LLIntersection)
- newtype Polyline = Polyline [Vec2]
- polylineLength :: Polyline -> Double
- polylineEdges :: Polyline -> [Line]
- newtype Polygon = Polygon [Vec2]
- normalizePolygon :: Polygon -> Polygon
- data PolygonError
- = NotEnoughCorners Int
- | IdenticalPoints [Vec2]
- | SelfIntersections [(Line, Line)]
- validatePolygon :: Polygon -> Either PolygonError Polygon
- pointInPolygon :: Vec2 -> Polygon -> Bool
- polygonAverage :: Polygon -> Vec2
- polygonCentroid :: Polygon -> Vec2
- polygonCircumference :: Polygon -> Double
- polygonArea :: Polygon -> Double
- signedPolygonArea :: Polygon -> Double
- polygonEdges :: Polygon -> [Line]
- polygonAngles :: Polygon -> [Angle]
- isConvex :: Polygon -> Bool
- convexHull :: Foldable list => list Vec2 -> Polygon
- data PolygonOrientation
- polygonOrientation :: Polygon -> PolygonOrientation
- growPolygon :: Double -> Polygon -> Polygon
- shrinkPolygon :: Double -> Polygon -> Polygon
- data Circle = Circle {
- _circleCenter :: !Vec2
- _circleRadius :: !Double
- newtype UnsafeTransformCircle = UnsafeTransformCircle Circle
- toEllipse :: Circle -> Ellipse
- newtype Ellipse = Ellipse Transformation
- data Angle
- deg :: Double -> Angle
- getDeg :: Angle -> Double
- rad :: Double -> Angle
- getRad :: Angle -> Double
- normalizeAngle :: Angle -> Angle -> Angle
- pseudoAngle :: Vec2 -> Double
- class VectorSpace v where
- vsum :: VectorSpace a => [a] -> a
- class Transform geo where
- transform :: Transformation -> geo -> geo
- data Transformation = Transformation !Mat2 !Vec2
- newtype NoTransform a = NoTransform a
- translate :: Vec2 -> Transformation
- rotate :: Angle -> Transformation
- rotateAround :: Vec2 -> Angle -> Transformation
- scale :: Double -> Transformation
- scale' :: Double -> Double -> Transformation
- scaleAround :: Vec2 -> Double -> Transformation
- scaleAround' :: Vec2 -> Double -> Double -> Transformation
- mirrorAlong :: Line -> Transformation
- mirrorXCoords :: Transformation
- mirrorYCoords :: Transformation
- shear :: Double -> Double -> Transformation
- decomposeTransformation :: Transformation -> (Vec2, (Double, Double), Double, Angle)
- class HasBoundingBox a where
- boundingBox :: a -> BoundingBox
- data BoundingBox = BoundingBox !Vec2 !Vec2
- newtype NoBoundingBox a = NoBoundingBox a
- overlappingBoundingBoxes :: (HasBoundingBox a, HasBoundingBox b) => a -> b -> Bool
- transformBoundingBox :: (HasBoundingBox source, HasBoundingBox target) => source -> target -> TransformBBSettings -> Transformation
- data FitDimension
- data FitAspect
- data FitAlign
- data TransformBBSettings = TransformBBSettings {}
- boundingBoxPolygon :: HasBoundingBox object => object -> Polygon
- insideBoundingBox :: (HasBoundingBox thing, HasBoundingBox bigObject) => thing -> bigObject -> Bool
- boundingBoxCenter :: HasBoundingBox a => a -> Vec2
- boundingBoxIntersection :: (HasBoundingBox a, HasBoundingBox b) => a -> b -> Maybe BoundingBox
- boundingBoxSize :: HasBoundingBox a => a -> (Double, Double)
- growBoundingBox :: HasBoundingBox boundingBox => Double -> boundingBox -> BoundingBox
- shrinkBoundingBox :: HasBoundingBox boundingBox => Double -> boundingBox -> BoundingBox
- data Mat2 = Mat2 !Double !Double !Double !Double
- det :: Mat2 -> Double
- mulMV :: Mat2 -> Vec2 -> Vec2
- mulVTM :: Vec2 -> Mat2 -> Vec2
- vectorOf :: Line -> Vec2
- cross :: Vec2 -> Vec2 -> Double
- direction :: Line -> Vec2
- module Data.Sequential
- class Monoid a => Group a where
- inverse :: a -> a
Primitives
2D Vectors
Instances
Show Vec2 Source # | |
NFData Vec2 Source # | |
Defined in Geometry.Core | |
VectorSpace Vec2 Source # | |
ChaosSource Vec2 Source # | |
MwcChaosSource Vec2 Source # | |
HasBoundingBox Vec2 Source # | |
Defined in Geometry.Core boundingBox :: Vec2 -> BoundingBox Source # | |
Transform Vec2 Source # | |
Defined in Geometry.Core | |
Eq Vec2 Source # | |
Ord Vec2 Source # | |
UniformRange Vec2 Source # | |
Defined in Geometry.Core |
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).
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)
>>>
:{
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
Line, defined by beginning and end.
Instances
Show Line Source # | |
NFData Line Source # | |
Defined in Geometry.Core | |
Sketch Line Source # | (image code)
|
Plotting Line Source # | |
ChaosSource Line Source # | |
MwcChaosSource Line Source # | |
HasBoundingBox Line Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Line -> BoundingBox Source # | |
Transform Line Source # | |
Defined in Geometry.Core | |
Eq Line Source # | |
Ord Line Source # | |
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.
lineLength :: Line -> Double Source #
Where do you end up when walking Distance
on a Line
?
moveAlong (Line start end) 0 == start moveAlong (Line start end) (lineLength …) == end
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)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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
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 #
IntersectionReal Vec2 | Two lines intersect fully. |
IntersectionVirtualInsideL Vec2 | The intersection is in the left argument (of |
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 |
Instances
Show LLIntersection Source # | |
Defined in Geometry.Core showsPrec :: Int -> LLIntersection -> ShowS # show :: LLIntersection -> String # showList :: [LLIntersection] -> ShowS # | |
Eq LLIntersection Source # | |
Defined in Geometry.Core (==) :: LLIntersection -> LLIntersection -> Bool # (/=) :: LLIntersection -> LLIntersection -> Bool # | |
Ord LLIntersection Source # | |
Defined in Geometry.Core compare :: LLIntersection -> LLIntersection -> Ordering # (<) :: LLIntersection -> LLIntersection -> Bool # (<=) :: LLIntersection -> LLIntersection -> Bool # (>) :: LLIntersection -> LLIntersection -> Bool # (>=) :: LLIntersection -> LLIntersection -> Bool # max :: LLIntersection -> LLIntersection -> LLIntersection # min :: LLIntersection -> LLIntersection -> LLIntersection # |
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).
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)
>>>
:{
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 #
:: 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)
>>>
:{
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
:: 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)
>>>
:{
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
Explicit type for polylines. Useful in type signatures, beacuse [[[Vec2]]] is
really hard to read. Also makes some typeclass instances clearer, such as
sketch
.
Instances
Show Polyline Source # | |
NFData Polyline Source # | |
Defined in Geometry.Core | |
Sketch Polyline Source # | Polyline, i.e. a sequence of lines given by their joints. (image code)
|
Plotting Polyline Source # | |
HasBoundingBox Polyline Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Polyline -> BoundingBox Source # | |
Transform Polyline Source # | |
Defined in Geometry.Core | |
Eq Polyline Source # | |
Ord Polyline Source # | |
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
Polygon, defined by its corners.
Many algorithms assume certain invariants about polygons, see
validatePolygon
for details.
Instances
Show Polygon Source # | |
NFData Polygon Source # | |
Defined in Geometry.Core | |
Sketch Polygon Source # | (image code)
|
Plotting Polygon Source # | |
ChaosSource Polygon Source # | |
MwcChaosSource Polygon Source # | |
HasBoundingBox Polygon Source # | |
Defined in Geometry.Core boundingBox :: Polygon -> BoundingBox Source # | |
Transform Polygon Source # | |
Defined in Geometry.Core | |
Eq Polygon Source # | |
Ord Polygon Source # | |
normalizePolygon :: Polygon -> Polygon Source #
List-rotate the polygon’s corners until the minimum is the first entry in the corner list.
data PolygonError Source #
Instances
Show PolygonError Source # | |
Defined in Geometry.Core showsPrec :: Int -> PolygonError -> ShowS # show :: PolygonError -> String # showList :: [PolygonError] -> ShowS # | |
Eq PolygonError Source # | |
Defined in Geometry.Core (==) :: PolygonError -> PolygonError -> Bool # (/=) :: PolygonError -> PolygonError -> Bool # | |
Ord PolygonError Source # | |
Defined in Geometry.Core compare :: PolygonError -> PolygonError -> Ordering # (<) :: PolygonError -> PolygonError -> Bool # (<=) :: PolygonError -> PolygonError -> Bool # (>) :: PolygonError -> PolygonError -> Bool # (>=) :: PolygonError -> PolygonError -> Bool # max :: PolygonError -> PolygonError -> PolygonError # min :: PolygonError -> PolygonError -> PolygonError # |
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)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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
Polygon
s 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)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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
data PolygonOrientation Source #
Orientation of a polygon
PolygonPositive | Counter-clockwise when plotted on a standard math coordinate system |
PolygonNegative | Clockwise |
Instances
Show PolygonOrientation Source # | |
Defined in Geometry.Core showsPrec :: Int -> PolygonOrientation -> ShowS # show :: PolygonOrientation -> String # showList :: [PolygonOrientation] -> ShowS # | |
Eq PolygonOrientation Source # | |
Defined in Geometry.Core (==) :: PolygonOrientation -> PolygonOrientation -> Bool # (/=) :: PolygonOrientation -> PolygonOrientation -> Bool # | |
Ord PolygonOrientation Source # | |
Defined in Geometry.Core compare :: PolygonOrientation -> PolygonOrientation -> Ordering # (<) :: PolygonOrientation -> PolygonOrientation -> Bool # (<=) :: PolygonOrientation -> PolygonOrientation -> Bool # (>) :: PolygonOrientation -> PolygonOrientation -> Bool # (>=) :: PolygonOrientation -> PolygonOrientation -> Bool # max :: PolygonOrientation -> PolygonOrientation -> PolygonOrientation # min :: PolygonOrientation -> PolygonOrientation -> PolygonOrientation # |
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)
>>>
:{
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)
>>>
:{
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
Circles are not an instance of Transform
, because e.g. shear
ing a circle
yields an Ellipse
. To transform circles, convert them to an ellipse first with
toEllipse
.
Circle | |
|
Instances
Show Circle Source # | |
Default Circle Source # | Unit circle |
Defined in Geometry.Core | |
NFData Circle Source # | |
Defined in Geometry.Core | |
Sketch Circle Source # | (image code)
|
Plotting Circle Source # | |
HasBoundingBox Circle Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Circle -> BoundingBox Source # | |
Eq Circle Source # | |
Ord Circle Source # | |
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
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:
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. |
Defined in Geometry.Core |
An Ellipse
is a Transformation
applied to the unit Circle
. Create them
using toEllipse
and by then applying Transformation
s to it.
(image code)
>>>
:{
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
Instances
Show Ellipse Source # | |
Default Ellipse Source # | Unit circle |
Defined in Geometry.Core | |
NFData Ellipse Source # | |
Defined in Geometry.Core | |
Sketch Ellipse Source # | (image code)
|
Plotting Ellipse Source # | Approximation by a number of points |
HasBoundingBox Ellipse Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Ellipse -> BoundingBox Source # | |
Transform Ellipse Source # | |
Defined in Geometry.Core |
Angles
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.
:: 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 Angle
s – 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)
>>>
:{
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 Angle
s – 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.
(+.) :: 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
Neutral element
Inverse element
Instances
VectorSpace Angle Source # | |
VectorSpace Mat2 Source # | |
VectorSpace Vec2 Source # | |
VectorSpace PhaseSpace Source # | |
Defined in Physics (+.) :: PhaseSpace -> PhaseSpace -> PhaseSpace Source # (-.) :: PhaseSpace -> PhaseSpace -> PhaseSpace Source # (*.) :: Double -> PhaseSpace -> PhaseSpace Source # (/.) :: PhaseSpace -> Double -> PhaseSpace Source # zero :: PhaseSpace Source # negateV :: PhaseSpace -> PhaseSpace Source # | |
VectorSpace Double Source # | |
VectorSpace a => VectorSpace (NBody a) Source # | |
VectorSpace b => VectorSpace (a -> b) Source # | |
(VectorSpace v1, VectorSpace v2) => VectorSpace (v1, v2) Source # | |
(VectorSpace v1, VectorSpace v2, VectorSpace v3) => VectorSpace (v1, v2, v3) Source # | |
(VectorSpace v1, VectorSpace v2, VectorSpace v3, VectorSpace v4) => VectorSpace (v1, v2, v3, v4) Source # | |
Defined in Algebra.VectorSpace (+.) :: (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 # | |
(VectorSpace v1, VectorSpace v2, VectorSpace v3, VectorSpace v4, VectorSpace v5) => VectorSpace (v1, v2, v3, v4, v5) Source # | |
Defined in Algebra.VectorSpace (+.) :: (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)
transform :: Transformation -> geo -> geo Source #
Instances
Transform Bezier Source # | |
Defined in Geometry.Bezier | |
Transform BoundingBox Source # | |
Defined in Geometry.Core transform :: Transformation -> BoundingBox -> BoundingBox Source # | |
Transform Ellipse Source # | |
Defined in Geometry.Core | |
Transform Line Source # | |
Defined in Geometry.Core | |
Transform Polygon Source # | |
Defined in Geometry.Core | |
Transform Polyline Source # | |
Defined in Geometry.Core | |
Transform Transformation Source # | |
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:
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. |
Defined in Geometry.Core | |
Transform Vec2 Source # | |
Defined in Geometry.Core | |
Transform Tile Source # | |
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 |
Defined in Geometry.Core | |
Transform a => Transform (NoBoundingBox a) Source # | |
Defined in Geometry.Core transform :: Transformation -> NoBoundingBox a -> NoBoundingBox a Source # | |
Transform (NoTransform a) Source # | Don’t transform the contents. |
Defined in Geometry.Core transform :: Transformation -> NoTransform a -> NoTransform a Source # | |
Transform a => Transform (Vector a) Source # | |
Defined in Geometry.Core transform :: Transformation -> Vector a -> Vector a Source # | |
Transform a => Transform [a] Source # | |
Defined in Geometry.Core transform :: Transformation -> [a] -> [a] Source # | |
(Transform a, Transform b) => Transform (Either a b) Source # | |
Defined in Geometry.Core | |
Transform a => Transform (Map k a) Source # | |
Defined in Geometry.Core | |
Transform b => Transform (a -> b) Source # | Transform the result of a function. moveRight :: |
Defined in Geometry.Core transform :: Transformation -> (a -> b) -> a -> b Source # | |
(Transform a, Transform b) => Transform (a, b) Source # | |
Defined in Geometry.Core transform :: Transformation -> (a, b) -> (a, b) Source # | |
(Transform a, Transform b, Transform c) => Transform (a, b, c) Source # | |
Defined in Geometry.Core transform :: Transformation -> (a, b, c) -> (a, b, c) Source # | |
(Transform a, Transform b, Transform c, Transform d) => Transform (a, b, c, d) Source # | |
Defined in Geometry.Core 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 # | |
Defined in Geometry.Core 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} \]
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
Monoid Transformation Source # | |
Defined in Geometry.Core mappend :: Transformation -> Transformation -> Transformation # mconcat :: [Transformation] -> Transformation # | |
Semigroup Transformation Source # | The order transformations are applied in function order:
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 |
Defined in Geometry.Core (<>) :: Transformation -> Transformation -> Transformation # sconcat :: NonEmpty Transformation -> Transformation # stimes :: Integral b => b -> Transformation -> Transformation # | |
Show Transformation Source # | |
Defined in Geometry.Core showsPrec :: Int -> Transformation -> ShowS # show :: Transformation -> String # showList :: [Transformation] -> ShowS # | |
NFData Transformation Source # | |
Defined in Geometry.Core rnf :: Transformation -> () # | |
Group Transformation Source # | |
Defined in Geometry.Core | |
Sketch Transformation Source # | Draw a \(100\times 100\) square with its corner at (image code)
|
Defined in Draw sketch :: Transformation -> Render () Source # | |
Transform Transformation Source # | |
Defined in Geometry.Core | |
Eq Transformation Source # | |
Defined in Geometry.Core (==) :: Transformation -> Transformation -> Bool # (/=) :: Transformation -> Transformation -> Bool # | |
Ord Transformation Source # | |
Defined in Geometry.Core compare :: Transformation -> Transformation -> Ordering # (<) :: Transformation -> Transformation -> Bool # (<=) :: Transformation -> Transformation -> Bool # (>) :: Transformation -> Transformation -> Bool # (>=) :: Transformation -> Transformation -> Bool # max :: Transformation -> Transformation -> Transformation # min :: Transformation -> Transformation -> Transformation # |
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.
Instances
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 Vec2
s in the target.
(image code)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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 #
:: 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.
boundingBox :: a -> BoundingBox Source #
Instances
HasBoundingBox Canvas Source # | |
Defined in Draw.Plotting.CmdArgs boundingBox :: Canvas -> BoundingBox Source # | |
HasBoundingBox Bezier Source # | (image code)
|
Defined in Geometry.Bezier boundingBox :: Bezier -> BoundingBox Source # | |
HasBoundingBox BoundingBox Source # | This is simply |
Defined in Geometry.Core boundingBox :: BoundingBox -> BoundingBox Source # | |
HasBoundingBox Circle Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Circle -> BoundingBox Source # | |
HasBoundingBox Ellipse Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Ellipse -> BoundingBox Source # | |
HasBoundingBox Line Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Line -> BoundingBox Source # | |
HasBoundingBox Polygon Source # | |
Defined in Geometry.Core boundingBox :: Polygon -> BoundingBox Source # | |
HasBoundingBox Polyline Source # | (image code)
|
Defined in Geometry.Core boundingBox :: Polyline -> BoundingBox Source # | |
HasBoundingBox Vec2 Source # | |
Defined in Geometry.Core boundingBox :: Vec2 -> BoundingBox Source # | |
HasBoundingBox SvgElement Source # | |
Defined in Geometry.SvgParser boundingBox :: SvgElement -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox (Set a) Source # | |
Defined in Geometry.Core boundingBox :: Set a -> BoundingBox Source # | |
HasBoundingBox (NoBoundingBox a) Source # | Contents are ignored, reporting an empty bounding box. |
Defined in Geometry.Core boundingBox :: NoBoundingBox a -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox (NoTransform a) Source # | |
Defined in Geometry.Core boundingBox :: NoTransform a -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox (Vector a) Source # | |
Defined in Geometry.Core boundingBox :: Vector a -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox (Maybe a) Source # | |
Defined in Geometry.Core boundingBox :: Maybe a -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox [a] Source # | |
Defined in Geometry.Core boundingBox :: [a] -> BoundingBox Source # | |
(HasBoundingBox a, HasBoundingBox b) => HasBoundingBox (Either a b) Source # | |
Defined in Geometry.Core boundingBox :: Either a b -> BoundingBox Source # | |
HasBoundingBox a => HasBoundingBox (Map k a) Source # | |
Defined in Geometry.Core boundingBox :: Map k a -> BoundingBox Source # | |
(HasBoundingBox a, HasBoundingBox b) => HasBoundingBox (a, b) Source # | |
Defined in Geometry.Core boundingBox :: (a, b) -> BoundingBox Source # | |
(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c) => HasBoundingBox (a, b, c) Source # | |
Defined in Geometry.Core boundingBox :: (a, b, c) -> BoundingBox Source # | |
(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c, HasBoundingBox d) => HasBoundingBox (a, b, c, d) Source # | |
Defined in Geometry.Core boundingBox :: (a, b, c, d) -> BoundingBox Source # | |
(HasBoundingBox a, HasBoundingBox b, HasBoundingBox c, HasBoundingBox d, HasBoundingBox e) => HasBoundingBox (a, b, c, d, e) Source # | |
Defined in Geometry.Core 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
instead of boundingBox
(a,b)
.BoundingBox
a b
Instances
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 |
Defined in Geometry.Core mempty :: BoundingBox # mappend :: BoundingBox -> BoundingBox -> BoundingBox # mconcat :: [BoundingBox] -> BoundingBox # | |
Semigroup BoundingBox Source # | |
Defined in Geometry.Core (<>) :: BoundingBox -> BoundingBox -> BoundingBox # sconcat :: NonEmpty BoundingBox -> BoundingBox # stimes :: Integral b => b -> BoundingBox -> BoundingBox # | |
Show BoundingBox Source # | |
Defined in Geometry.Core showsPrec :: Int -> BoundingBox -> ShowS # show :: BoundingBox -> String # showList :: [BoundingBox] -> ShowS # | |
NFData BoundingBox Source # | |
Defined in Geometry.Core rnf :: BoundingBox -> () # | |
Sketch BoundingBox Source # | Sketches a rectangle with a diagonal cross through it. Useful for debugging. (image code)
|
Defined in Draw sketch :: BoundingBox -> Render () Source # | |
Plotting BoundingBox Source # | Trace the bounding box without actually drawing anything to estimate result size |
Defined in Draw.Plotting plot :: BoundingBox -> Plot () Source # | |
ChaosSource BoundingBox Source # | |
Defined in Geometry.Chaotic perturb :: BoundingBox -> Int Source # | |
MwcChaosSource BoundingBox Source # | |
Defined in Geometry.Chaotic mwcChaos :: BoundingBox -> Word32 Source # | |
HasBoundingBox BoundingBox Source # | This is simply |
Defined in Geometry.Core boundingBox :: BoundingBox -> BoundingBox Source # | |
Transform BoundingBox Source # | |
Defined in Geometry.Core transform :: Transformation -> BoundingBox -> BoundingBox Source # | |
Eq BoundingBox Source # | |
Defined in Geometry.Core (==) :: BoundingBox -> BoundingBox -> Bool # (/=) :: BoundingBox -> BoundingBox -> Bool # | |
Ord BoundingBox Source # | |
Defined in Geometry.Core compare :: BoundingBox -> BoundingBox -> Ordering # (<) :: BoundingBox -> BoundingBox -> Bool # (<=) :: BoundingBox -> BoundingBox -> Bool # (>) :: BoundingBox -> BoundingBox -> Bool # (>=) :: BoundingBox -> BoundingBox -> Bool # max :: BoundingBox -> BoundingBox -> BoundingBox # min :: BoundingBox -> BoundingBox -> BoundingBox # |
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.
Instances
overlappingBoundingBoxes :: (HasBoundingBox a, HasBoundingBox b) => a -> b -> Bool Source #
Do the bounding boxes of two objects overlap?
:: (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 #
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 |
Instances
Show FitDimension Source # | |
Defined in Geometry.Core showsPrec :: Int -> FitDimension -> ShowS # show :: FitDimension -> String # showList :: [FitDimension] -> ShowS # | |
Eq FitDimension Source # | |
Defined in Geometry.Core (==) :: FitDimension -> FitDimension -> Bool # (/=) :: FitDimension -> FitDimension -> Bool # | |
Ord FitDimension Source # | |
Defined in Geometry.Core compare :: FitDimension -> FitDimension -> Ordering # (<) :: FitDimension -> FitDimension -> Bool # (<=) :: FitDimension -> FitDimension -> Bool # (>) :: FitDimension -> FitDimension -> Bool # (>=) :: FitDimension -> FitDimension -> Bool # max :: FitDimension -> FitDimension -> FitDimension # min :: FitDimension -> FitDimension -> FitDimension # |
MaintainAspect | Maintain width:height aspect ratio |
IgnoreAspect | Ignore aspect ratio |
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 |
data TransformBBSettings Source #
transformBoundingBox
settings paramter. If you don’t care for the details, use def
.
Instances
boundingBoxPolygon :: HasBoundingBox object => object -> Polygon Source #
The rectangle representing a BoundingBox
, with positive orientation.
(image code)
>>>
:{
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)
>>>
:{
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)
>>>
:{
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 #
:: (HasBoundingBox a, HasBoundingBox b) | |
=> a | |
-> b | |
-> Maybe BoundingBox |
|
Bounding box of the intersection of two bounding boxes. This is the
intersection analogon to <>
representing union.
(image code)
>>>
:{
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)
>>>
:{
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
:: 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)
>>>
:{
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)
>>>
:{
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
\(2\times2\) matrix.
Mat2 !Double !Double !Double !Double |
|
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)
>>>
:{
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
module Data.Sequential