brillo-rendering-2.0.0: Brillo/Internals/Rendering/Polygon.hs
{-# OPTIONS_HADDOCK hide #-}
module Brillo.Internals.Rendering.Polygon (
renderComplexPolygon,
renderComplexPolygonSmooth,
) where
import Brillo.Internals.Rendering.Common
import Graphics.Rendering.OpenGL (($=))
import Graphics.Rendering.OpenGL.GL qualified as GL
import Graphics.Rendering.OpenGL.GLU.Tessellation
combiner :: a -> b -> ()
combiner _ _ = ()
-- written this way to measurably improve performance
zipLoop :: [a] -> [(a, a)]
zipLoop [] = []
zipLoop (x : xs) =
let
go y [] = [(y, x)]
go y (z : rs) = (y, z) : go z rs
in
go x xs
zipWithLoop :: (a -> a -> b) -> [a] -> [b]
zipWithLoop f = map (uncurry f) . zipLoop
{-| Signed angle between 2 vectors
https://stackoverflow.com/a/16544330/1779797
Note: `isConvex` would remain correct if this returned a value
between theta*7/2pi and theta*7/4pi
which probably provides an opportunity for optimization
-}
angle :: (Float, Float) -> (Float, Float) -> Float
angle (x1, y1) (x2, y2) =
let dot = x1 * x2 + y1 * y2 -- cos theta * |v1||v2|
det = y2 * x1 - x2 * y1 -- sin theta * |v1||v2|
in atan2 det dot
{-| Approximating 2pi by 7 is reasonable here.
The total rotation theoretically must be a multiple of 2pi,
so a little generosity doesn't break anything,
but might save some cases that would fail due to floating point errors
-}
isConvex :: [(Float, Float)] -> Bool
isConvex ps =
let
-- Combine angles, but return a value greater than 7
-- if they have opposite signs
angleAdd :: Float -> Float -> Float
angleAdd a b = if signum a * signum b < -0.5 then 10 else a + b
in
-- Check that it doesn't turn more than one full circle in total
all (\theta -> (theta <= 7) && (theta > -7)) $
scanl angleAdd 0 $ -- Check that the path's direction is consistent
zipWithLoop angle $ -- Compute angles of turns at each vertex
filter (/= (0, 0)) $ -- Discard edges arising from duplicated vertices
-- Produce vectors for each edge
zipWithLoop (\(x1, y1) (x2, y2) -> (x2 - x1, y2 - y1)) ps
renderComplexPolygon :: [(Float, Float)] -> IO ()
renderComplexPolygon path =
if isConvex path
then GL.renderPrimitive GL.Polygon $ vertexPFs path
else do
Triangulation ts <-
triangulate TessWindingOdd 0 (GL.Normal3 0 0 1) combiner $
ComplexPolygon
[ ComplexContour
[ AnnotatedVertex (GL.Vertex3 (realToFrac a) (realToFrac b) 0) ()
| (a, b) <- path
]
]
GL.renderPrimitive GL.Triangles (trisToGLVertices ts)
return ()
{-| Render a complex polygon with anti-aliased edges
Draws the filled polygon, then overlays anti-aliased edges
-}
renderComplexPolygonSmooth :: [(Float, Float)] -> IO ()
renderComplexPolygonSmooth path = do
-- First draw the filled polygon
renderComplexPolygon path
-- Then draw anti-aliased edges on top
GL.lineSmooth $= GL.Enabled
GL.renderPrimitive GL.LineLoop $ vertexPFs path
GL.lineSmooth $= GL.Disabled
trisToGLVertices :: [Triangle a] -> IO ()
trisToGLVertices [] = return ()
trisToGLVertices
( (Triangle (AnnotatedVertex v1 _) (AnnotatedVertex v2 _) (AnnotatedVertex v3 _))
: rest
) =
do
GL.vertex v1
GL.vertex v2
GL.vertex v3
trisToGLVertices rest
{-# INLINE trisToGLVertices #-}
vertexPFs :: [(Float, Float)] -> IO ()
vertexPFs [] = return ()
vertexPFs ((x, y) : rest) =
do
GL.vertex $ GL.Vertex2 (gf x) (gf y)
vertexPFs rest
{-# INLINE vertexPFs #-}