packages feed

reanimate 0.4.1.0 → 0.4.2.0

raw patch · 137 files changed

+2618/−5178 lines, 137 filesdep +filelockdep +ghciddep +hgeometrydep −chiphunkdep −earcutdep −hmatrixdep ~ansi-terminaldep ~arraydep ~attoparsecbinary-addedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: filelock, ghcid, hgeometry, hgeometry-combinatorial

Dependencies removed: chiphunk, earcut, hmatrix

Dependency ranges changed: ansi-terminal, array, attoparsec, base64-bytestring, bytestring, cassava, cereal, colour, containers, cubicbezier, directory, filepath, fsnotify, geojson, hashable, lens, linear, matrix, mtl, open-browser, optparse-applicative, parallel, process, random, random-shuffle, split, temporary, text, time, vector-space, xml

API changes (from Hackage documentation)

- Reanimate: (#) :: a -> (a -> b) -> b
- Reanimate: Animation :: Duration -> (Time -> SVG) -> Animation
- Reanimate: fromListS :: [(Double, Signal)] -> Signal
- Reanimate.Animation: (#) :: a -> (a -> b) -> b
- Reanimate.Animation: Animation :: Duration -> (Time -> SVG) -> Animation
- Reanimate.Animation: pauseUntil :: Duration -> Animation -> Animation
- Reanimate.Builtin.Documentation: drawBall :: Animation
- Reanimate.Builtin.Flip: flipTransitionOpts :: Double -> Double -> Double -> Transition
- Reanimate.Builtin.TernaryPlot: atCenter :: Double -> Tree -> Tree
- Reanimate.Builtin.TernaryPlot: radius :: Double
- Reanimate.Chiphunk: addToBodyStore :: BodyStore -> Body -> Tree -> IO ()
- Reanimate.Chiphunk: newBodyStore :: IO BodyStore
- Reanimate.Chiphunk: polyShapesToBody :: Space -> [PolyShape] -> IO Body
- Reanimate.Chiphunk: polygonsToBody :: Space -> [[Vect]] -> IO Body
- Reanimate.Chiphunk: simulate :: Space -> BodyStore -> Double -> Int -> Double -> IO Animation
- Reanimate.Chiphunk: spaceFreeRecursive :: Space -> IO ()
- Reanimate.Chiphunk: type BodyStore = IORef (Map WordPtr Tree)
- Reanimate.ColorSpace: lightLABCoordinates :: Map Nanometer (Double, Double, Double)
- Reanimate.ColorSpace: renderLABCoordinates :: Tree
- Reanimate.ColorSpace: renderSensitivity :: Tree
- Reanimate.ColorSpace: renderXYZCoordinates :: Tree
- Reanimate.Ease: fromListS :: [(Double, Signal)] -> Signal
- Reanimate.Math.Balloon: balloonP :: Polygon -> Double -> Polygon
- Reanimate.Math.Balloon: diameter :: Polygon -> Double
- Reanimate.Math.Balloon: shiftLongestDiameter :: Polygon -> Polygon
- Reanimate.Math.Balloon: ssspDistances :: Polygon -> Vector Double
- Reanimate.Math.Balloon: takeUntil :: (a -> Bool) -> [a] -> [a]
- Reanimate.Math.Common: epsEq :: (Ord a, Fractional a) => a -> a -> Bool
- Reanimate.Math.Common: epsilon :: Fractional a => a
- Reanimate.Math.Compatible: Mesh :: Points -> Edges -> Mesh
- Reanimate.Math.Compatible: MeshPair :: Points -> Points -> Edges -> MeshPair
- Reanimate.Math.Compatible: OneLink :: Link
- Reanimate.Math.Compatible: TwoLink :: Link
- Reanimate.Math.Compatible: [meshEdges] :: Mesh -> Edges
- Reanimate.Math.Compatible: [meshPoints] :: Mesh -> Points
- Reanimate.Math.Compatible: compatiblyTriangulateP :: Polygon -> Polygon -> [(Polygon, Polygon)]
- Reanimate.Math.Compatible: compatiblyTriangulateP' :: Polygon -> Polygon -> Polygon -> [(Polygon, Polygon)]
- Reanimate.Math.Compatible: data Link
- Reanimate.Math.Compatible: data Mesh
- Reanimate.Math.Compatible: data MeshPair
- Reanimate.Math.Compatible: mkSteinerPoints :: V2 Rational -> V2 Rational -> Int -> [V2 Rational]
- Reanimate.Math.Compatible: oneBendBetween :: Polygon -> Int -> Int -> Bool
- Reanimate.Math.Compatible: polygonOneLinks :: Polygon -> [(Int, Int)]
- Reanimate.Math.Compatible: polygonTwoLinks :: Polygon -> [(Int, Int)]
- Reanimate.Math.Compatible: selectContains :: Polygon -> Polygon -> V2 Rational -> Polygon
- Reanimate.Math.Compatible: showStep :: Polygon -> Polygon -> SVG
- Reanimate.Math.Compatible: split1Link :: Polygon -> Int -> Int -> Int -> (Polygon, Polygon)
- Reanimate.Math.Compatible: split2Link :: Polygon -> Int -> Int -> (Polygon, Polygon)
- Reanimate.Math.Compatible: splitNLink :: Polygon -> Int -> [(Link, Int)] -> (Polygon, Polygon)
- Reanimate.Math.Compatible: splitNLinks :: Polygon -> Int -> [(Link, Int)] -> [V2 Rational]
- Reanimate.Math.Compatible: steiner2Link :: Polygon -> Int -> Int -> V2 Rational
- Reanimate.Math.Compatible: truncateP :: V2 Rational -> V2 Rational
- Reanimate.Math.Compatible: type Edges = [(Int, Int, Int)]
- Reanimate.Math.Compatible: type Points = Vector (V2 Rational)
- Reanimate.Math.DCEL: Edge :: EdgeId -> VertexId -> EdgeId -> EdgeId -> EdgeId -> FaceId -> Edge
- Reanimate.Math.DCEL: Face :: FaceId -> EdgeId -> Face
- Reanimate.Math.DCEL: Mesh :: Int -> FaceId -> Map VertexId (Vertex a) -> Map EdgeId Edge -> Map FaceId Face -> Mesh a
- Reanimate.Math.DCEL: Vertex :: VertexId -> a -> Vertex a
- Reanimate.Math.DCEL: [_edgeFace] :: Edge -> FaceId
- Reanimate.Math.DCEL: [_edgeId] :: Edge -> EdgeId
- Reanimate.Math.DCEL: [_edgeNext] :: Edge -> EdgeId
- Reanimate.Math.DCEL: [_edgePrev] :: Edge -> EdgeId
- Reanimate.Math.DCEL: [_edgeTwin] :: Edge -> EdgeId
- Reanimate.Math.DCEL: [_edgeVertex] :: Edge -> VertexId
- Reanimate.Math.DCEL: [_faceEdge] :: Face -> EdgeId
- Reanimate.Math.DCEL: [_faceId] :: Face -> FaceId
- Reanimate.Math.DCEL: [_meshEdges] :: Mesh a -> Map EdgeId Edge
- Reanimate.Math.DCEL: [_meshFaces] :: Mesh a -> Map FaceId Face
- Reanimate.Math.DCEL: [_meshIdCounter] :: Mesh a -> Int
- Reanimate.Math.DCEL: [_meshOuterFace] :: Mesh a -> FaceId
- Reanimate.Math.DCEL: [_meshVertices] :: Mesh a -> Map VertexId (Vertex a)
- Reanimate.Math.DCEL: [_vertexId] :: Vertex a -> VertexId
- Reanimate.Math.DCEL: [_vertexPosition] :: Vertex a -> a
- Reanimate.Math.DCEL: angleSmooth :: V2 Double -> Vector (V2 Double) -> V2 Double
- Reanimate.Math.DCEL: applyCompatible :: (t -> Mesh a -> Maybe (Mesh a)) -> (Mesh a -> [t]) -> Mesh a -> Mesh a -> (Mesh a, Mesh a)
- Reanimate.Math.DCEL: buildMesh :: State (Mesh a) b -> Mesh a
- Reanimate.Math.DCEL: createEdge :: VertexId -> State (Mesh a) EdgeId
- Reanimate.Math.DCEL: createFace :: State (Mesh a) FaceId
- Reanimate.Math.DCEL: createVertex :: a -> State (Mesh a) VertexId
- Reanimate.Math.DCEL: data Edge
- Reanimate.Math.DCEL: data Face
- Reanimate.Math.DCEL: data Mesh a
- Reanimate.Math.DCEL: data Vertex a
- Reanimate.Math.DCEL: delaunayFlip :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))
- Reanimate.Math.DCEL: delaunayFlip' :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))
- Reanimate.Math.DCEL: deleteEdge :: EdgeId -> MeshM a ()
- Reanimate.Math.DCEL: deleteFace :: FaceId -> MeshM a ()
- Reanimate.Math.DCEL: edgeFace :: Lens' Edge FaceId
- Reanimate.Math.DCEL: edgeId :: Lens' Edge EdgeId
- Reanimate.Math.DCEL: edgeLength :: EdgeId -> Mesh (V2 Double) -> Double
- Reanimate.Math.DCEL: edgeNext :: Lens' Edge EdgeId
- Reanimate.Math.DCEL: edgePrev :: Lens' Edge EdgeId
- Reanimate.Math.DCEL: edgeTwin :: Lens' Edge EdgeId
- Reanimate.Math.DCEL: edgeVertex :: Lens' Edge VertexId
- Reanimate.Math.DCEL: emptyMesh :: Mesh a
- Reanimate.Math.DCEL: faceEdge :: Lens' Face EdgeId
- Reanimate.Math.DCEL: faceEdges :: FaceId -> Mesh a -> [EdgeId]
- Reanimate.Math.DCEL: faceId :: Lens' Face FaceId
- Reanimate.Math.DCEL: faceMinAngle :: FaceId -> Mesh (V2 Double) -> Double
- Reanimate.Math.DCEL: facePositions :: FaceId -> Mesh a -> [a]
- Reanimate.Math.DCEL: findEdge :: FaceId -> VertexId -> MeshM a Edge
- Reanimate.Math.DCEL: findEdge' :: VertexId -> VertexId -> MeshM a (Maybe Edge)
- Reanimate.Math.DCEL: findEdge'' :: [FaceId] -> VertexId -> MeshM a (Maybe Edge)
- Reanimate.Math.DCEL: flipEdge :: Edge -> MeshM (V2 Double) (FaceId, FaceId)
- Reanimate.Math.DCEL: getEdge :: EdgeId -> State (Mesh a) Edge
- Reanimate.Math.DCEL: getFace :: FaceId -> State (Mesh a) Face
- Reanimate.Math.DCEL: getVertex :: Eq a => a -> State (Mesh a) VertexId
- Reanimate.Math.DCEL: insertEdge :: FaceId -> VertexId -> VertexId -> MeshM a (FaceId, FaceId)
- Reanimate.Math.DCEL: instance GHC.Show.Show Reanimate.Math.DCEL.Edge
- Reanimate.Math.DCEL: instance GHC.Show.Show Reanimate.Math.DCEL.Face
- Reanimate.Math.DCEL: instance GHC.Show.Show a => GHC.Show.Show (Reanimate.Math.DCEL.Mesh a)
- Reanimate.Math.DCEL: instance GHC.Show.Show a => GHC.Show.Show (Reanimate.Math.DCEL.Vertex a)
- Reanimate.Math.DCEL: internalEdges :: Mesh a -> [EdgeId]
- Reanimate.Math.DCEL: isValidLocation :: V2 Double -> Vector (V2 Double) -> V2 Double -> Bool
- Reanimate.Math.DCEL: linkEdges :: EdgeId -> EdgeId -> State (Mesh a) ()
- Reanimate.Math.DCEL: longestEdge :: Mesh (V2 Double) -> Mesh (V2 Double) -> EdgeId
- Reanimate.Math.DCEL: meshAngles :: Mesh (V2 Double) -> [Double]
- Reanimate.Math.DCEL: meshEdgeAngle :: Mesh (V2 Double) -> EdgeId -> Double
- Reanimate.Math.DCEL: meshEdges :: forall a_a24S2. Lens' (Mesh a_a24S2) (Map EdgeId Edge)
- Reanimate.Math.DCEL: meshFaces :: forall a_a24S2. Lens' (Mesh a_a24S2) (Map FaceId Face)
- Reanimate.Math.DCEL: meshGetEdge :: EdgeId -> Mesh a -> Edge
- Reanimate.Math.DCEL: meshGetFace :: FaceId -> Mesh a -> Face
- Reanimate.Math.DCEL: meshGetVertex :: VertexId -> Mesh a -> Vertex a
- Reanimate.Math.DCEL: meshIdCounter :: forall a_a24S2. Lens' (Mesh a_a24S2) Int
- Reanimate.Math.DCEL: meshOuterFace :: forall a_a24S2. Lens' (Mesh a_a24S2) FaceId
- Reanimate.Math.DCEL: meshSmoothPosition :: Mesh (V2 Double) -> Mesh (V2 Double)
- Reanimate.Math.DCEL: meshVertices :: forall a_a24S2 a_a25bh. Lens (Mesh a_a24S2) (Mesh a_a25bh) (Map VertexId (Vertex a_a24S2)) (Map VertexId (Vertex a_a25bh))
- Reanimate.Math.DCEL: minAngle :: V2 Double -> Vector (V2 Double) -> Double
- Reanimate.Math.DCEL: modifyEdge :: (Edge -> Edge) -> EdgeId -> State (Mesh a) ()
- Reanimate.Math.DCEL: modifyFace :: (Face -> Face) -> FaceId -> State (Mesh a) ()
- Reanimate.Math.DCEL: modifyVertex :: (Vertex a -> Vertex a) -> VertexId -> State (Mesh a) ()
- Reanimate.Math.DCEL: newId :: MeshM a Int
- Reanimate.Math.DCEL: numEdges :: Mesh a -> Int
- Reanimate.Math.DCEL: numFaces :: Mesh a -> Int
- Reanimate.Math.DCEL: numVertices :: Mesh a -> Int
- Reanimate.Math.DCEL: outerEdges :: Mesh a -> [EdgeId]
- Reanimate.Math.DCEL: outgoingEdges :: VertexId -> Mesh a -> [Edge]
- Reanimate.Math.DCEL: polygonMesh :: [a] -> MeshM a ()
- Reanimate.Math.DCEL: polygonMeshOuter :: [a] -> MeshM a ()
- Reanimate.Math.DCEL: polygonsMesh :: Eq a => [a] -> [[a]] -> MeshM a ()
- Reanimate.Math.DCEL: ppMesh :: Show a => Mesh a -> String
- Reanimate.Math.DCEL: renderMesh :: Double -> Mesh (V2 Double) -> SVG
- Reanimate.Math.DCEL: renderMeshColored :: Mesh (V2 Double) -> SVG
- Reanimate.Math.DCEL: renderMeshEdges :: Mesh (V2 Double) -> SVG
- Reanimate.Math.DCEL: renderMeshSimple :: Double -> Mesh (V2 Double) -> SVG
- Reanimate.Math.DCEL: renderMeshStats :: Mesh (V2 Double) -> SVG
- Reanimate.Math.DCEL: requireVertex :: VertexId -> MeshM a (Vertex a)
- Reanimate.Math.DCEL: setFace :: EdgeId -> FaceId -> State (Mesh a) ()
- Reanimate.Math.DCEL: setFaceEdge :: FaceId -> EdgeId -> MeshM a ()
- Reanimate.Math.DCEL: setNextEdge :: EdgeId -> EdgeId -> State (Mesh a) ()
- Reanimate.Math.DCEL: setPreviousEdge :: EdgeId -> EdgeId -> State (Mesh a) ()
- Reanimate.Math.DCEL: setTwinEdge :: EdgeId -> EdgeId -> MeshM a ()
- Reanimate.Math.DCEL: smoothVertex :: VertexId -> MeshM (V2 Double) ()
- Reanimate.Math.DCEL: splitEdge :: VertexId -> EdgeId -> MeshM a EdgeId
- Reanimate.Math.DCEL: splitInternalEdge :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))
- Reanimate.Math.DCEL: splitInternalEdgeForced :: EdgeId -> Mesh (V2 Double) -> Mesh (V2 Double)
- Reanimate.Math.DCEL: splitInternalEdges :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))
- Reanimate.Math.DCEL: splitLongestEdge :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))
- Reanimate.Math.DCEL: splitOuterEdge :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))
- Reanimate.Math.DCEL: splitOuterEdges :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))
- Reanimate.Math.DCEL: splitOuterTriangle :: V2 Double -> EdgeId -> MeshM (V2 Double) (FaceId, FaceId)
- Reanimate.Math.DCEL: splitTriangle :: V2 Double -> EdgeId -> MeshM (V2 Double) (FaceId, FaceId, FaceId, FaceId)
- Reanimate.Math.DCEL: steinerNodes :: Mesh a -> [VertexId]
- Reanimate.Math.DCEL: toRigidMesh :: Mesh (V2 Double) -> Mesh (V2 Double) -> Mesh
- Reanimate.Math.DCEL: type EdgeId = Int
- Reanimate.Math.DCEL: type FaceId = Int
- Reanimate.Math.DCEL: type MeshM position a = State (Mesh position) a
- Reanimate.Math.DCEL: type VertexId = Int
- Reanimate.Math.DCEL: updateFaces :: EdgeId -> FaceId -> State (Mesh a) ()
- Reanimate.Math.DCEL: validMesh :: Mesh a -> [String]
- Reanimate.Math.DCEL: vertexId :: forall a_a24S1. Lens' (Vertex a_a24S1) VertexId
- Reanimate.Math.DCEL: vertexPosition :: forall a_a24S1 a_a25eo. Lens (Vertex a_a24S1) (Vertex a_a25eo) a_a24S1 a_a25eo
- Reanimate.Math.DCEL: vertices :: Mesh a -> [Vertex a]
- Reanimate.Math.DCEL: withEdge :: EdgeId -> (Edge -> State (Mesh a) ()) -> State (Mesh a) ()
- Reanimate.Math.EarClip: earClip :: (Fractional a, Ord a) => Ring a -> Triangulation
- Reanimate.Math.EarClip: earClip' :: (Fractional a, Ord a) => Ring a -> [Triangulation]
- Reanimate.Math.EarClip: earCut :: Real a => Ring a -> Triangulation
- Reanimate.Math.EarClip: earCut' :: Real a => Ring a -> [Triangulation]
- Reanimate.Math.EarClip: isEarCorner :: (Fractional a, Ord a) => Ring a -> [Int] -> Int -> Int -> Int -> Bool
- Reanimate.Math.Polygon: Polygon :: Vector (V2 a) -> Int -> Triangulation -> Vector SSSP -> APolygon a
- Reanimate.Math.Polygon: [polygonOffset] :: APolygon a -> Int
- Reanimate.Math.Polygon: [polygonPoints] :: APolygon a -> Vector (V2 a)
- Reanimate.Math.Polygon: [polygonSSSP] :: APolygon a -> Vector SSSP
- Reanimate.Math.Polygon: [polygonTriangulation] :: APolygon a -> Triangulation
- Reanimate.Math.Polygon: concave :: Polygon
- Reanimate.Math.Polygon: data APolygon a
- Reanimate.Math.Polygon: instance (GHC.Real.Real a, GHC.Real.Fractional a, GHC.Classes.Ord a, Data.Serialize.Serialize a) => Data.Serialize.Serialize (Reanimate.Math.Polygon.APolygon a)
- Reanimate.Math.Polygon: instance Data.Hashable.Class.Hashable a => Data.Hashable.Class.Hashable (Reanimate.Math.Polygon.APolygon a)
- Reanimate.Math.Polygon: instance GHC.Show.Show a => GHC.Show.Show (Reanimate.Math.Polygon.APolygon a)
- Reanimate.Math.Polygon: isValidTriangulation :: Polygon -> Triangulation -> Bool
- Reanimate.Math.Polygon: mkPolygon :: (Real a, Fractional a, Ord a) => Vector (V2 a) -> APolygon a
- Reanimate.Math.Polygon: mkPolygonFromRing :: (Real a, Fractional a, Ord a) => Ring a -> APolygon a
- Reanimate.Math.Polygon: pAccess :: APolygon a -> Int -> V2 a
- Reanimate.Math.Polygon: pAddPoints :: Int -> Polygon -> Polygon
- Reanimate.Math.Polygon: pAdjustOffset :: APolygon a -> Int -> APolygon a
- Reanimate.Math.Polygon: pArea :: Polygon -> Rational
- Reanimate.Math.Polygon: pAtCenter :: Polygon -> Polygon
- Reanimate.Math.Polygon: pAtCentroid :: Polygon -> Polygon
- Reanimate.Math.Polygon: pBoundingBox :: Polygon -> (Rational, Rational, Rational, Rational)
- Reanimate.Math.Polygon: pCenter :: Polygon -> V2 Rational
- Reanimate.Math.Polygon: pCentroid :: Polygon -> V2 Rational
- Reanimate.Math.Polygon: pCircumference :: (Real a, Fractional a) => APolygon a -> a
- Reanimate.Math.Polygon: pCircumference' :: (Real a, Fractional a) => APolygon a -> Double
- Reanimate.Math.Polygon: pCopy :: Polygon -> Polygon
- Reanimate.Math.Polygon: pCutEqual :: Polygon -> (Polygon, Polygon)
- Reanimate.Math.Polygon: pCuts :: Polygon -> [(Polygon, Polygon)]
- Reanimate.Math.Polygon: pCycle :: (Real a, Fractional a, Ord a) => APolygon a -> Double -> APolygon a
- Reanimate.Math.Polygon: pCycles :: Polygon -> [Polygon]
- Reanimate.Math.Polygon: pDeoverlap :: Polygon -> Polygon
- Reanimate.Math.Polygon: pGenerate :: [(Double, Double)] -> Polygon
- Reanimate.Math.Polygon: pIsCCW :: Polygon -> Bool
- Reanimate.Math.Polygon: pIsConvex :: Polygon -> Bool
- Reanimate.Math.Polygon: pIsInside :: Polygon -> V2 Rational -> Bool
- Reanimate.Math.Polygon: pIsSimple :: Polygon -> Bool
- Reanimate.Math.Polygon: pMapEdges :: (V2 Rational -> V2 Rational -> a) -> Polygon -> Vector a
- Reanimate.Math.Polygon: pMkWinding :: Int -> Polygon
- Reanimate.Math.Polygon: pNext :: APolygon a -> Int -> Int
- Reanimate.Math.Polygon: pNull :: APolygon a -> Bool
- Reanimate.Math.Polygon: pParent :: Polygon -> Int -> Int -> Int
- Reanimate.Math.Polygon: pPrev :: APolygon a -> Int -> Int
- Reanimate.Math.Polygon: pRayIntersect :: Polygon -> (Int, Int) -> (Int, Int) -> Maybe (V2 Rational)
- Reanimate.Math.Polygon: pRing :: APolygon a -> Ring a
- Reanimate.Math.Polygon: pScale :: Rational -> Polygon -> Polygon
- Reanimate.Math.Polygon: pSetOffset :: APolygon a -> Int -> APolygon a
- Reanimate.Math.Polygon: pSize :: APolygon a -> Int
- Reanimate.Math.Polygon: pTranslate :: V2 Rational -> Polygon -> Polygon
- Reanimate.Math.Polygon: pUnGenerate :: Polygon -> [(Double, Double)]
- Reanimate.Math.Polygon: pUnsafeMap :: (Ring a -> Ring a) -> APolygon a -> APolygon a
- Reanimate.Math.Polygon: pdualPolygons :: Polygon -> PDual -> [Polygon]
- Reanimate.Math.Polygon: shape1 :: Polygon
- Reanimate.Math.Polygon: shape10 :: Polygon
- Reanimate.Math.Polygon: shape11 :: Polygon
- Reanimate.Math.Polygon: shape12 :: Polygon
- Reanimate.Math.Polygon: shape13 :: Polygon
- Reanimate.Math.Polygon: shape14 :: Polygon
- Reanimate.Math.Polygon: shape15 :: Polygon
- Reanimate.Math.Polygon: shape16 :: Polygon
- Reanimate.Math.Polygon: shape17 :: Polygon
- Reanimate.Math.Polygon: shape18 :: Polygon
- Reanimate.Math.Polygon: shape19 :: Polygon
- Reanimate.Math.Polygon: shape2 :: Polygon
- Reanimate.Math.Polygon: shape20 :: Polygon
- Reanimate.Math.Polygon: shape21 :: Polygon
- Reanimate.Math.Polygon: shape22 :: Polygon
- Reanimate.Math.Polygon: shape23 :: Polygon
- Reanimate.Math.Polygon: shape3 :: Polygon
- Reanimate.Math.Polygon: shape4 :: Polygon
- Reanimate.Math.Polygon: shape5 :: Polygon
- Reanimate.Math.Polygon: shape6 :: Polygon
- Reanimate.Math.Polygon: shape7 :: Polygon
- Reanimate.Math.Polygon: shape8 :: Polygon
- Reanimate.Math.Polygon: shape9 :: Polygon
- Reanimate.Math.Polygon: ssspVisibility :: Polygon -> Polygon
- Reanimate.Math.Polygon: ssspWindows :: Polygon -> [(V2 Rational, V2 Rational)]
- Reanimate.Math.Polygon: triangle :: Polygon
- Reanimate.Math.Polygon: triangle' :: [P]
- Reanimate.Math.Polygon: triangulationsToPolygons :: Polygon -> Triangulation -> [Polygon]
- Reanimate.Math.Polygon: type FPolygon = APolygon Double
- Reanimate.Math.Polygon: type P = V2 Double
- Reanimate.Math.Polygon: type Polygon = APolygon Rational
- Reanimate.Math.Render: drawSSSP :: Polygon -> Animation
- Reanimate.Math.Render: drawSSSPNaive :: Polygon -> Animation
- Reanimate.Math.Render: drawTriangulation :: Polygon -> (Ring Rational -> [Triangulation]) -> Animation
- Reanimate.Math.Render: polygonDots :: Polygon -> SVG
- Reanimate.Math.Render: polygonNumDots :: Polygon -> SVG
- Reanimate.Math.Render: polygonShape :: Polygon -> SVG
- Reanimate.Math.Render: renderDual :: Ring Rational -> Dual -> SVG
- Reanimate.Math.Render: renderSSSP :: Polygon -> SSSP -> SVG
- Reanimate.Math.Render: renderTriangulation :: Polygon -> Triangulation -> SVG
- Reanimate.Math.SSSP: Dual :: (Int, Int, Int) -> DualTree -> DualTree -> Dual
- Reanimate.Math.SSSP: EmptyDual :: DualTree
- Reanimate.Math.SSSP: NodeDual :: Int -> DualTree -> DualTree -> DualTree
- Reanimate.Math.SSSP: data Dual
- Reanimate.Math.SSSP: data DualTree
- Reanimate.Math.SSSP: data PDual
- Reanimate.Math.SSSP: drawDual :: Dual -> String
- Reanimate.Math.SSSP: dual :: Int -> Triangulation -> Dual
- Reanimate.Math.SSSP: dualToTriangulation :: Ring Rational -> Dual -> Triangulation
- Reanimate.Math.SSSP: instance GHC.Show.Show Reanimate.Math.SSSP.Dual
- Reanimate.Math.SSSP: instance GHC.Show.Show Reanimate.Math.SSSP.DualTree
- Reanimate.Math.SSSP: instance GHC.Show.Show Reanimate.Math.SSSP.PDual
- Reanimate.Math.SSSP: naive :: Ring Rational -> SSSP
- Reanimate.Math.SSSP: naive2 :: Ring Rational -> SSSP
- Reanimate.Math.SSSP: pdualReduce :: Ring Rational -> PDual -> Int -> PDual
- Reanimate.Math.SSSP: pdualRings :: Ring Rational -> PDual -> [Ring Rational]
- Reanimate.Math.SSSP: sssp :: (Fractional a, Ord a) => Ring a -> Dual -> SSSP
- Reanimate.Math.SSSP: toPDual :: Ring Rational -> Dual -> PDual
- Reanimate.Math.SSSP: type SSSP = Vector Int
- Reanimate.Math.SSSP: visibilityArray :: Ring Rational -> Vector [Int]
- Reanimate.Math.Smooth: angleSmooth :: V2 Double -> Vector (V2 Double) -> V2 Double
- Reanimate.Math.Smooth: meshMinAngle :: Mesh -> (Double, Double)
- Reanimate.Math.Smooth: renderAMesh :: Mesh -> SVG
- Reanimate.Math.Smooth: renderMesh :: Polygon -> [Polygon] -> SVG
- Reanimate.Math.Smooth: smoothMesh :: Mesh -> [Mesh]
- Reanimate.Math.Smooth: smoothStep :: forall s. MVector s [Int] -> MVector s (V2 Double) -> Vector Int -> ST s ()
- Reanimate.Math.Smooth: splitMeshEdges :: Double -> Mesh -> Mesh
- Reanimate.Math.Smooth: steinerPoints :: Polygon -> [Polygon] -> [V2 Rational]
- Reanimate.Math.Triangulate: edgesToTriangulation :: Int -> [(Int, Int)] -> Triangulation
- Reanimate.Math.Triangulate: edgesToTriangulationM :: Int -> [(Int, Int)] -> ST s (MVector s [Int])
- Reanimate.Math.Triangulate: trianglesToTriangulation :: Int -> Vector (Int, Int, Int) -> Triangulation
- Reanimate.Math.Triangulate: trianglesToTriangulationM :: Int -> Vector (Int, Int, Int) -> ST s (MVector s [Int])
- Reanimate.Math.Triangulate: type Triangulation = Vector [Int]
- Reanimate.Math.Visibility: fastForward :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]
- Reanimate.Math.Visibility: go :: (Ord a, Fractional a) => V2 a -> [V2 a] -> [V2 a] -> [V2 a]
- Reanimate.Math.Visibility: rightTurn :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]
- Reanimate.Math.Visibility: scanc :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]
- Reanimate.Math.Visibility: unwindStack :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> t -> [V2 a]
- Reanimate.Math.Visibility: visibility :: [P] -> [P]
- Reanimate.Misc: renameOrCopyFile :: FilePath -> FilePath -> IO ()
- Reanimate.Misc: requireExecutable :: String -> IO FilePath
- Reanimate.Misc: runCmd :: FilePath -> [String] -> IO ()
- Reanimate.Misc: runCmdLazy :: FilePath -> [String] -> (IO (Either String Text) -> IO a) -> IO a
- Reanimate.Misc: runCmd_ :: FilePath -> [String] -> IO (Either String String)
- Reanimate.Misc: withTempDir :: (FilePath -> IO a) -> IO a
- Reanimate.Misc: withTempFile :: String -> (FilePath -> IO a) -> IO a
- Reanimate.Morph.Cache: cachePointCorrespondence :: Int -> PointCorrespondence -> PointCorrespondence
- Reanimate.Morph.LeastWork: BendCosts :: Double -> Double -> Double -> Double -> BendCosts
- Reanimate.Morph.LeastWork: StretchCosts :: Double -> Double -> Double -> StretchCosts
- Reanimate.Morph.LeastWork: [bendDeviationPenalty] :: BendCosts -> Double
- Reanimate.Morph.LeastWork: [bendElasticity] :: BendCosts -> Double
- Reanimate.Morph.LeastWork: [bendStiffness] :: BendCosts -> Double
- Reanimate.Morph.LeastWork: [bendZeroPenalty] :: BendCosts -> Double
- Reanimate.Morph.LeastWork: [stretchCollapsePenalty] :: StretchCosts -> Double
- Reanimate.Morph.LeastWork: [stretchElasticity] :: StretchCosts -> Double
- Reanimate.Morph.LeastWork: [stretchStiffness] :: StretchCosts -> Double
- Reanimate.Morph.LeastWork: anyLeastWork :: StretchCosts -> BendCosts -> PointCorrespondence
- Reanimate.Morph.LeastWork: bendInfo :: V2 PType -> V2 PType -> V2 PType -> V2 PType -> (Double, Double, Bool)
- Reanimate.Morph.LeastWork: bendWork :: BendCosts -> V2 PType -> V2 PType -> V2 PType -> V2 PType -> Double
- Reanimate.Morph.LeastWork: data BendCosts
- Reanimate.Morph.LeastWork: data StretchCosts
- Reanimate.Morph.LeastWork: defaultBendCosts :: BendCosts
- Reanimate.Morph.LeastWork: defaultStretchCosts :: StretchCosts
- Reanimate.Morph.LeastWork: instance Data.Hashable.Class.Hashable Reanimate.Morph.LeastWork.BendCosts
- Reanimate.Morph.LeastWork: instance Data.Hashable.Class.Hashable Reanimate.Morph.LeastWork.StretchCosts
- Reanimate.Morph.LeastWork: leastWork :: StretchCosts -> BendCosts -> PointCorrespondence
- Reanimate.Morph.LeastWork: leastWork' :: StretchCosts -> BendCosts -> Polygon -> Polygon -> (Polygon, Polygon, Double)
- Reanimate.Morph.LeastWork: rawLeastWork :: StretchCosts -> BendCosts -> PointCorrespondence
- Reanimate.Morph.LeastWork: stretchWork :: StretchCosts -> Double -> Double -> Double
- Reanimate.Morph.LeastWork: zeroStretchCosts :: StretchCosts
- Reanimate.Morph.LineBend: lineBend :: Trajectory
- Reanimate.Morph.LineBend: lineBendRaw :: Trajectory
- Reanimate.Morph.Linear: linearCorrespondence :: PointCorrespondence
- Reanimate.Morph.Rigid: Mesh :: Vector P -> Vector P -> Vector Int -> Vector RelTrig -> Mesh
- Reanimate.Morph.Rigid: Prep :: (P, P) -> Vector P -> Vector P -> Vector (Matrix Double, Matrix Double) -> Vector (Matrix Double, Matrix Double) -> GMatrix -> Prep
- Reanimate.Morph.Rigid: [meshOutline] :: Mesh -> Vector Int
- Reanimate.Morph.Rigid: [meshPointsA] :: Mesh -> Vector P
- Reanimate.Morph.Rigid: [meshPointsB] :: Mesh -> Vector P
- Reanimate.Morph.Rigid: [meshTriangles] :: Mesh -> Vector RelTrig
- Reanimate.Morph.Rigid: [prepPivot] :: Prep -> (P, P)
- Reanimate.Morph.Rigid: [prepPointsA] :: Prep -> Vector P
- Reanimate.Morph.Rigid: [prepPointsB] :: Prep -> Vector P
- Reanimate.Morph.Rigid: [prepRSRev] :: Prep -> Vector (Matrix Double, Matrix Double)
- Reanimate.Morph.Rigid: [prepRS] :: Prep -> Vector (Matrix Double, Matrix Double)
- Reanimate.Morph.Rigid: [prepUToB] :: Prep -> GMatrix
- Reanimate.Morph.Rigid: applyA :: Matrix Double -> Trig -> Trig
- Reanimate.Morph.Rigid: applyCoeff :: Matrix Double -> Trig -> Matrix Double
- Reanimate.Morph.Rigid: coeffOfB :: Trig -> Matrix Double
- Reanimate.Morph.Rigid: computeA :: Trig -> Trig -> Matrix Double
- Reanimate.Morph.Rigid: computeA_RSt :: Matrix Double -> Matrix Double -> Double -> Matrix Double
- Reanimate.Morph.Rigid: computeRS :: Matrix Double -> (Matrix Double, Matrix Double)
- Reanimate.Morph.Rigid: data Mesh
- Reanimate.Morph.Rigid: data Prep
- Reanimate.Morph.Rigid: interpolate :: Prep -> Double -> Vector P
- Reanimate.Morph.Rigid: matrixToQuaternion :: Matrix Double -> Quaternion Double
- Reanimate.Morph.Rigid: prepare :: Mesh -> Prep
- Reanimate.Morph.Rigid: quaternionToMatrix :: Quaternion Double -> Matrix Double
- Reanimate.Morph.Rigid: renderMeshPair :: Mesh -> SVG
- Reanimate.Morph.Rigid: symmetric :: Bool
- Reanimate.Morph.Rigid: trigToMatrix :: Trig -> Matrix Double
- Reanimate.Morph.Rigid: type P = V2 Double
- Reanimate.Morph.Rigid: type RelTrig = (Int, Int, Int)
- Reanimate.Morph.Rigid: type Trig = (P, P, P)
- Reanimate.PolyShape: PolyShapeWithHoles :: PolyShape -> [PolyShape] -> PolyShapeWithHoles
- Reanimate.PolyShape: [polyShapeHoles] :: PolyShapeWithHoles -> [PolyShape]
- Reanimate.PolyShape: [polyShapeParent] :: PolyShapeWithHoles -> PolyShape
- Reanimate.PolyShape: plPartial' :: Double -> ([DPoint], PolyShape) -> PolyShape
- Reanimate.PolyShape: plPartialGroup :: Double -> [PolyShape] -> [PolyShape]
- Reanimate.PolyShape: plPolygonify :: Double -> PolyShape -> [Point Double]
- Reanimate.PolyShape: polyShapeTolerance :: Double
- Reanimate.PolyShape: splitPolyShape :: Double -> Int -> PolyShape -> [PolyShape]
- Reanimate.Scene: asAnimation :: (forall s'. Scene s' a) -> Scene s Animation
- Reanimate.Svg.BoundingBox: linePoints :: [LineCommand] -> [RPoint]
- Reanimate.Svg.BoundingBox: svgBoundingPoints :: Tree -> [RPoint]
- Reanimate.Svg.LineCommand: adjustLineLength :: Double -> RPoint -> LineCommand -> LineCommand
- Reanimate.Svg.LineCommand: calculateVectorAngle :: Double -> Double -> Double -> Double -> Double
- Reanimate.Svg.LineCommand: cmdToControlPoint :: LineCommand -> Maybe RPoint
- Reanimate.Svg.LineCommand: convertSvgArc :: RPoint -> Coord -> Coord -> Coord -> Bool -> Bool -> RPoint -> [LineCommand]
- Reanimate.Svg.LineCommand: interpolatePathCommands :: Double -> [PathCommand] -> [PathCommand]
- Reanimate.Svg.LineCommand: mkStraightLine :: RPoint -> LineCommand
- Reanimate.Svg.LineCommand: partialBezierPoints :: [RPoint] -> Double -> Double -> [RPoint]
- Reanimate.Svg.LineCommand: partialLine :: Double -> [LineCommand] -> [LineCommand]
- Reanimate.Svg.LineCommand: rpointsToBezier :: [RPoint] -> CubicBezier Double
- Reanimate.Svg.LineCommand: toBezierPoint :: RPoint -> Point Double
- Reanimate.Svg.LineCommand: toLineCommand :: RPoint -> Maybe RPoint -> PathCommand -> CmdM [LineCommand]
+ Geom2D.CubicBezier.Linear: AnyBezier :: Vector (V2 a) -> AnyBezier a
+ Geom2D.CubicBezier.Linear: ClosedMetaPath :: [(V2 a, MetaJoin a)] -> ClosedMetaPath a
+ Geom2D.CubicBezier.Linear: ClosedPath :: [(V2 a, PathJoin a)] -> ClosedPath a
+ Geom2D.CubicBezier.Linear: Controls :: V2 a -> V2 a -> MetaJoin a
+ Geom2D.CubicBezier.Linear: CubicBezier :: !V2 a -> !V2 a -> !V2 a -> !V2 a -> CubicBezier a
+ Geom2D.CubicBezier.Linear: Curl :: a -> MetaNodeType a
+ Geom2D.CubicBezier.Linear: Direction :: V2 a -> MetaNodeType a
+ Geom2D.CubicBezier.Linear: FillEvenOdd :: FillRule
+ Geom2D.CubicBezier.Linear: FillNonZero :: FillRule
+ Geom2D.CubicBezier.Linear: JoinCurve :: V2 a -> V2 a -> PathJoin a
+ Geom2D.CubicBezier.Linear: JoinLine :: PathJoin a
+ Geom2D.CubicBezier.Linear: MetaJoin :: MetaNodeType a -> Tension a -> Tension a -> MetaNodeType a -> MetaJoin a
+ Geom2D.CubicBezier.Linear: Open :: MetaNodeType a
+ Geom2D.CubicBezier.Linear: OpenMetaPath :: [(V2 a, MetaJoin a)] -> V2 a -> OpenMetaPath a
+ Geom2D.CubicBezier.Linear: OpenPath :: [(V2 a, PathJoin a)] -> V2 a -> OpenPath a
+ Geom2D.CubicBezier.Linear: QuadBezier :: !V2 a -> !V2 a -> !V2 a -> QuadBezier a
+ Geom2D.CubicBezier.Linear: Tension :: a -> Tension a
+ Geom2D.CubicBezier.Linear: TensionAtLeast :: a -> Tension a
+ Geom2D.CubicBezier.Linear: [cubicC0] :: CubicBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [cubicC1] :: CubicBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [cubicC2] :: CubicBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [cubicC3] :: CubicBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [curlgamma] :: MetaNodeType a -> a
+ Geom2D.CubicBezier.Linear: [metaTypeL] :: MetaJoin a -> MetaNodeType a
+ Geom2D.CubicBezier.Linear: [metaTypeR] :: MetaJoin a -> MetaNodeType a
+ Geom2D.CubicBezier.Linear: [nodedir] :: MetaNodeType a -> V2 a
+ Geom2D.CubicBezier.Linear: [quadC0] :: QuadBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [quadC1] :: QuadBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [quadC2] :: QuadBezier a -> !V2 a
+ Geom2D.CubicBezier.Linear: [tensionL] :: MetaJoin a -> Tension a
+ Geom2D.CubicBezier.Linear: [tensionR] :: MetaJoin a -> Tension a
+ Geom2D.CubicBezier.Linear: [tensionValue] :: Tension a -> a
+ Geom2D.CubicBezier.Linear: arcLength :: CubicBezier Double -> Double -> Double -> Double
+ Geom2D.CubicBezier.Linear: arcLengthParam :: CubicBezier Double -> Double -> Double -> Double
+ Geom2D.CubicBezier.Linear: bezierHoriz :: CubicBezier Double -> [Double]
+ Geom2D.CubicBezier.Linear: bezierIntersection :: CubicBezier Double -> CubicBezier Double -> Double -> [(Double, Double)]
+ Geom2D.CubicBezier.Linear: bezierSubsegment :: (Ord a, Unbox a, Fractional a, GenericBezier b) => b a -> a -> a -> b a
+ Geom2D.CubicBezier.Linear: bezierVert :: CubicBezier Double -> [Double]
+ Geom2D.CubicBezier.Linear: closedPathCurves :: Fractional a => ClosedPath a -> [CubicBezier a]
+ Geom2D.CubicBezier.Linear: closest :: CubicBezier Double -> V2 Double -> Double -> Double
+ Geom2D.CubicBezier.Linear: colinear :: CubicBezier Double -> Double -> Bool
+ Geom2D.CubicBezier.Linear: curvesToClosed :: [CubicBezier a] -> ClosedPath a
+ Geom2D.CubicBezier.Linear: data ClosedMetaPath a
+ Geom2D.CubicBezier.Linear: data ClosedPath a
+ Geom2D.CubicBezier.Linear: data CubicBezier a
+ Geom2D.CubicBezier.Linear: data FillRule
+ Geom2D.CubicBezier.Linear: data MetaJoin a
+ Geom2D.CubicBezier.Linear: data MetaNodeType a
+ Geom2D.CubicBezier.Linear: data OpenMetaPath a
+ Geom2D.CubicBezier.Linear: data OpenPath a
+ Geom2D.CubicBezier.Linear: data PathJoin a
+ Geom2D.CubicBezier.Linear: data QuadBezier a
+ Geom2D.CubicBezier.Linear: data Tension a
+ Geom2D.CubicBezier.Linear: evalBezier :: (GenericBezier b, Unbox a, Fractional a) => b a -> a -> V2 a
+ Geom2D.CubicBezier.Linear: evalBezierDeriv :: (Unbox a, Fractional a, GenericBezier b) => b a -> a -> (V2 a, V2 a)
+ Geom2D.CubicBezier.Linear: findBezierCusp :: CubicBezier Double -> [Double]
+ Geom2D.CubicBezier.Linear: findBezierInflection :: CubicBezier Double -> [Double]
+ Geom2D.CubicBezier.Linear: instance (Geom2D.CubicBezier.Linear.Cast a a', Geom2D.CubicBezier.Linear.Cast b b') => Geom2D.CubicBezier.Linear.Cast (a, b) (a', b')
+ Geom2D.CubicBezier.Linear: instance Data.Foldable.Foldable Geom2D.CubicBezier.Linear.Tension
+ Geom2D.CubicBezier.Linear: instance Data.Traversable.Traversable Geom2D.CubicBezier.Linear.Tension
+ Geom2D.CubicBezier.Linear: instance Data.Vector.Unboxed.Base.Unbox a => Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.AnyBezier a) (Geom2D.CubicBezier.Basic.AnyBezier a)
+ Geom2D.CubicBezier.Linear: instance GHC.Base.Functor Geom2D.CubicBezier.Linear.Tension
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.ClosedMetaPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.ClosedPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.CubicBezier a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.MetaJoin a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.MetaNodeType a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.OpenMetaPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.OpenPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.PathJoin a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.QuadBezier a)
+ Geom2D.CubicBezier.Linear: instance GHC.Classes.Eq a => GHC.Classes.Eq (Geom2D.CubicBezier.Linear.Tension a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.ClosedMetaPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.ClosedPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.CubicBezier a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.MetaJoin a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.MetaNodeType a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.OpenMetaPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.OpenPath a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.PathJoin a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.QuadBezier a)
+ Geom2D.CubicBezier.Linear: instance GHC.Show.Show a => GHC.Show.Show (Geom2D.CubicBezier.Linear.Tension a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Basic.GenericBezier Geom2D.CubicBezier.Linear.AnyBezier
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Basic.GenericBezier Geom2D.CubicBezier.Linear.CubicBezier
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Basic.GenericBezier Geom2D.CubicBezier.Linear.QuadBezier
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.ClosedMetaPath a) (Geom2D.CubicBezier.MetaPath.ClosedMetaPath a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.ClosedPath a) (Geom2D.CubicBezier.Basic.ClosedPath a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.CubicBezier a) (Geom2D.CubicBezier.Basic.CubicBezier a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.MetaJoin a) (Geom2D.CubicBezier.MetaPath.MetaJoin a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.MetaNodeType a) (Geom2D.CubicBezier.MetaPath.MetaNodeType a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.OpenMetaPath a) (Geom2D.CubicBezier.MetaPath.OpenMetaPath a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.OpenPath a) (Geom2D.CubicBezier.Basic.OpenPath a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.PathJoin a) (Geom2D.CubicBezier.Basic.PathJoin a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.QuadBezier a) (Geom2D.CubicBezier.Basic.QuadBezier a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Geom2D.CubicBezier.Linear.Tension a) (Geom2D.CubicBezier.MetaPath.Tension a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast (Linear.V2.V2 a) (Geom2D.Point a)
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast Graphics.SvgTree.Types.FillRule Geom2D.CubicBezier.Overlap.FillRule
+ Geom2D.CubicBezier.Linear: instance Geom2D.CubicBezier.Linear.Cast a b => Geom2D.CubicBezier.Linear.Cast [a] [b]
+ Geom2D.CubicBezier.Linear: interpolateVector :: Num a => V2 a -> V2 a -> a -> V2 a
+ Geom2D.CubicBezier.Linear: newtype AnyBezier a
+ Geom2D.CubicBezier.Linear: openPathCurves :: Fractional a => OpenPath a -> [CubicBezier a]
+ Geom2D.CubicBezier.Linear: quadToCubic :: Fractional a => QuadBezier a -> CubicBezier a
+ Geom2D.CubicBezier.Linear: reorient :: (GenericBezier b, Unbox a) => b a -> b a
+ Geom2D.CubicBezier.Linear: splitBezier :: (Unbox a, Fractional a, GenericBezier b) => b a -> a -> (b a, b a)
+ Geom2D.CubicBezier.Linear: union :: [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Geom2D.CubicBezier.Linear: unmetaClosed :: ClosedMetaPath Double -> ClosedPath Double
+ Geom2D.CubicBezier.Linear: unmetaOpen :: OpenMetaPath Double -> OpenPath Double
+ Geom2D.CubicBezier.Linear: vectorDistance :: Floating a => V2 a -> V2 a -> a
+ Reanimate: powerS :: Double -> Signal
+ Reanimate: spriteScope :: Scene s a -> Scene s a
+ Reanimate.Math.Common: class Num a => Epsilon a
+ Reanimate.Math.Common: instance Linear.Epsilon.Epsilon GHC.Real.Rational
+ Reanimate.Math.Common: isInsideStrict :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> V2 a -> Bool
+ Reanimate.Math.Common: nearZero :: Epsilon a => a -> Bool
+ Reanimate.Morph.Linear: closestLinearCorrespondenceA :: (Real a, Fractional a, Epsilon a) => APolygon a -> APolygon a -> (APolygon a, APolygon a)
+ Reanimate.Morph.Rotational: type Origin = (Double, Double)
+ Reanimate.Render: renderOneFrame :: FilePath -> Int -> Bool -> Int -> Animation -> IO ()
+ Reanimate.Scene: oNew :: Renderable a => a -> Scene s (Object s a)
+ Reanimate.Scene: scene :: (forall s. Scene s a) -> Animation
+ Reanimate.Voice: Phone :: Double -> Text -> Phone
+ Reanimate.Voice: [phoneDuration] :: Phone -> Double
+ Reanimate.Voice: [phoneType] :: Phone -> Text
+ Reanimate.Voice: data Phone
- Reanimate.Math.Common: direction :: Fractional a => V2 a -> V2 a -> V2 a -> a
+ Reanimate.Math.Common: direction :: Num a => V2 a -> V2 a -> V2 a -> a
- Reanimate.Math.Common: distSquared :: Fractional a => V2 a -> V2 a -> a
+ Reanimate.Math.Common: distSquared :: Num a => V2 a -> V2 a -> a
- Reanimate.Math.Common: isLeftTurn :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool
+ Reanimate.Math.Common: isLeftTurn :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool
- Reanimate.Math.Common: isLeftTurnOrLinear :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool
+ Reanimate.Math.Common: isLeftTurnOrLinear :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool
- Reanimate.Math.Common: isRightTurn :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool
+ Reanimate.Math.Common: isRightTurn :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool
- Reanimate.Math.Common: isRightTurnOrLinear :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool
+ Reanimate.Math.Common: isRightTurnOrLinear :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool
- Reanimate.Morph.Common: normalizePolygons :: Polygon -> Polygon -> (Polygon, Polygon)
+ Reanimate.Morph.Common: normalizePolygons :: (Real a, Fractional a, Epsilon a) => APolygon a -> APolygon a -> (APolygon a, APolygon a)
- Reanimate.PolyShape: decomposePolygon :: [Point Double] -> [[RPoint]]
+ Reanimate.PolyShape: decomposePolygon :: [RPoint] -> [[RPoint]]
- Reanimate.PolyShape: plGroupTouching :: [PolyShape] -> [[([DPoint], PolyShape)]]
+ Reanimate.PolyShape: plGroupTouching :: [PolyShape] -> [[([RPoint], PolyShape)]]
- Reanimate.Render: renderSvgs :: Animation -> IO ()
+ Reanimate.Render: renderSvgs :: FilePath -> Int -> Bool -> Animation -> IO ()
- Reanimate.Scene: circleRadius :: Iso' Circle Double
+ Reanimate.Scene: circleRadius :: Lens' Circle Double
- Reanimate.Transform: identity :: TMatrix
+ Reanimate.Transform: identity :: Matrix Coord
- Reanimate.Transform: mkMatrix :: Maybe [Transformation] -> TMatrix
+ Reanimate.Transform: mkMatrix :: Maybe [Transformation] -> Matrix Coord
- Reanimate.Transform: toTransformation :: TMatrix -> Transformation
+ Reanimate.Transform: toTransformation :: Matrix Coord -> Transformation
- Reanimate.Transform: transformPoint :: TMatrix -> RPoint -> RPoint
+ Reanimate.Transform: transformPoint :: Matrix Coord -> RPoint -> RPoint

Files

ChangeLog.md view
@@ -1,6 +1,22 @@ # Revision history for reanimate -## 0.4.0.0+## 0.4.2.0++* Web-viewer: Use GHCi to reload code, reducing latency by ~100x.+* Improve caching for blender and povray.+* Hide many internal modules.+* Move hmatrix dependency to a separate package.+* Use hgeometry instead of broken C++ code.+* Introduce the basics for a new graphical object system.+* Introduce online playground.+* Introduce interactive embedded player.+* Hide internal modules and bring documentation coverage up to 100%.++## 0.4.1.0 -- 2020-07-30++* Slightly improved error messages.++## 0.4.0.0 -- 2020-07-28  * Show progress when running ffmpeg. * Small improvements to the scene API.
+ data/github-icon-white.svg view
@@ -0,0 +1,6 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<svg width="256px" height="250px" viewBox="0 0 256 250" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">+    <g>+        <path d="M128.00106,0 C57.3172926,0 0,57.3066942 0,128.00106 C0,184.555281 36.6761997,232.535542 87.534937,249.460899 C93.9320223,250.645779 96.280588,246.684165 96.280588,243.303333 C96.280588,240.251045 96.1618878,230.167899 96.106777,219.472176 C60.4967585,227.215235 52.9826207,204.369712 52.9826207,204.369712 C47.1599584,189.574598 38.770408,185.640538 38.770408,185.640538 C27.1568785,177.696113 39.6458206,177.859325 39.6458206,177.859325 C52.4993419,178.762293 59.267365,191.04987 59.267365,191.04987 C70.6837675,210.618423 89.2115753,204.961093 96.5158685,201.690482 C97.6647155,193.417512 100.981959,187.77078 104.642583,184.574357 C76.211799,181.33766 46.324819,170.362144 46.324819,121.315702 C46.324819,107.340889 51.3250588,95.9223682 59.5132437,86.9583937 C58.1842268,83.7344152 53.8029229,70.715562 60.7532354,53.0843636 C60.7532354,53.0843636 71.5019501,49.6441813 95.9626412,66.2049595 C106.172967,63.368876 117.123047,61.9465949 128.00106,61.8978432 C138.879073,61.9465949 149.837632,63.368876 160.067033,66.2049595 C184.49805,49.6441813 195.231926,53.0843636 195.231926,53.0843636 C202.199197,70.715562 197.815773,83.7344152 196.486756,86.9583937 C204.694018,95.9223682 209.660343,107.340889 209.660343,121.315702 C209.660343,170.478725 179.716133,181.303747 151.213281,184.472614 C155.80443,188.444828 159.895342,196.234518 159.895342,208.176593 C159.895342,225.303317 159.746968,239.087361 159.746968,243.303333 C159.746968,246.709601 162.05102,250.70089 168.53925,249.443941 C219.370432,232.499507 256,184.536204 256,128.00106 C256,57.3066942 198.691187,0 128.00106,0 Z M47.9405593,182.340212 C47.6586465,182.976105 46.6581745,183.166873 45.7467277,182.730227 C44.8183235,182.312656 44.2968914,181.445722 44.5978808,180.80771 C44.8734344,180.152739 45.876026,179.97045 46.8023103,180.409216 C47.7328342,180.826786 48.2627451,181.702199 47.9405593,182.340212 Z M54.2367892,187.958254 C53.6263318,188.524199 52.4329723,188.261363 51.6232682,187.366874 C50.7860088,186.474504 50.6291553,185.281144 51.2480912,184.70672 C51.8776254,184.140775 53.0349512,184.405731 53.8743302,185.298101 C54.7115892,186.201069 54.8748019,187.38595 54.2367892,187.958254 Z M58.5562413,195.146347 C57.7719732,195.691096 56.4895886,195.180261 55.6968417,194.042013 C54.9125733,192.903764 54.9125733,191.538713 55.713799,190.991845 C56.5086651,190.444977 57.7719732,190.936735 58.5753181,192.066505 C59.3574669,193.22383 59.3574669,194.58888 58.5562413,195.146347 Z M65.8613592,203.471174 C65.1597571,204.244846 63.6654083,204.03712 62.5716717,202.981538 C61.4524999,201.94927 61.1409122,200.484596 61.8446341,199.710926 C62.5547146,198.935137 64.0575422,199.15346 65.1597571,200.200564 C66.2704506,201.230712 66.6095936,202.705984 65.8613592,203.471174 Z M75.3025151,206.281542 C74.9930474,207.284134 73.553809,207.739857 72.1039724,207.313809 C70.6562556,206.875043 69.7087748,205.700761 70.0012857,204.687571 C70.302275,203.678621 71.7478721,203.20382 73.2083069,203.659543 C74.6539041,204.09619 75.6035048,205.261994 75.3025151,206.281542 Z M86.046947,207.473627 C86.0829806,208.529209 84.8535871,209.404622 83.3316829,209.4237 C81.8013,209.457614 80.563428,208.603398 80.5464708,207.564772 C80.5464708,206.498591 81.7483088,205.631657 83.2786917,205.606221 C84.8005962,205.576546 86.046947,206.424403 86.046947,207.473627 Z M96.6021471,207.069023 C96.7844366,208.099171 95.7267341,209.156872 94.215428,209.438785 C92.7295577,209.710099 91.3539086,209.074206 91.1652603,208.052538 C90.9808515,206.996955 92.0576306,205.939253 93.5413813,205.66582 C95.054807,205.402984 96.4092596,206.021919 96.6021471,207.069023 Z" fill="#ffffff"></path>+    </g>+</svg>
+ docs/gifs/doc_augustP.gif view

binary file changed (absent → 442010 bytes)

+ docs/gifs/doc_balloon.gif view

binary file changed (absent → 29162 bytes)

+ docs/gifs/doc_cameraAttach.gif view

binary file changed (absent → 197692 bytes)

+ docs/gifs/doc_cameraFocus.gif view

binary file changed (absent → 476809 bytes)

+ docs/gifs/doc_cassiniP.gif view

binary file changed (absent → 31276 bytes)

+ docs/gifs/doc_cylindricalEqualAreaP.gif view

binary file changed (absent → 31325 bytes)

+ docs/gifs/doc_foucautP.gif view

binary file changed (absent → 406449 bytes)

+ docs/gifs/doc_githubWhiteIcon.gif view

binary file changed (absent → 5171 bytes)

+ docs/gifs/doc_lagrangeP.gif view

binary file changed (absent → 625831 bytes)

+ docs/gifs/doc_linear.gif view

binary file changed (absent → 401461 bytes)

+ docs/gifs/doc_powerS.gif view

binary file changed (absent → 12737 bytes)

+ docs/gifs/doc_rawLinear.gif view

binary file changed (absent → 401461 bytes)

+ docs/gifs/doc_rotationalTrajectory.gif view

binary file changed (absent → 461716 bytes)

+ docs/gifs/doc_slideDownT.gif view

binary file changed (absent → 71737 bytes)

docs/gifs/doc_slideLeftT.gif view

binary file changed (83234 → 86029 bytes)

+ docs/gifs/doc_slideUpT.gif view

binary file changed (absent → 73143 bytes)

+ docs/gifs/doc_spriteScope.gif view

binary file changed (absent → 18932 bytes)

docs/gifs/doc_ternaryPlot.gif view

binary file changed (5132 → 6497 bytes)

examples/demo_tangent.hs view
@@ -6,8 +6,6 @@ import           Control.Lens                    ((^.)) import           Control.Monad.State import qualified Data.Vector.Unboxed             as V-import           Geom2D.CubicBezier              (AnyBezier (..), Point (..),-                                                  evalBezierDeriv) import           Graphics.SvgTree                (Coord, Tree (..), mapTree,                                                   pathDefinition) import           Linear.Metric@@ -15,10 +13,13 @@ import           Linear.Vector import           Reanimate import           Reanimate.Builtin.Documentation+import           Geom2D.CubicBezier.Linear +piSvg :: SVG+piSvg = pathify $ lowerTransformations $ center $ scale 10 $ latex "s"+ main :: IO () main = reanimate $ docEnv $ mkAnimation 30 $ \t ->-  let piSvg = pathify $ lowerTransformations $ center $ scale 10 $ latex "s" in   mkGroup   [ mkBackgroundPixel rtfdBackgroundColor   , piSvg@@ -81,7 +82,7 @@         else do           let bezier = lineCommandToBezier from cmd               (pos, tangent) = evalBezierDeriv bezier frac-          pure $ (fromPoint pos, fromPoint tangent)+          pure $ (pos, tangent)     totalLen = evalState (sum <$> mapM lineLength cmds) zero     targetLen = totalLen * alpha @@ -89,18 +90,12 @@ lineCommandToBezier from line =   case line of     LineBezier [a] ->-      AnyBezier $ V.fromList [toTuple from, toTuple a]+      AnyBezier $ V.fromList [from, a]     LineBezier [a,b] ->-      AnyBezier $ V.fromList [toTuple from, toTuple a, toTuple b]+      AnyBezier $ V.fromList [from, a, b]     LineBezier [a,b,c] ->-      AnyBezier $ V.fromList [toTuple from, toTuple a, toTuple b, toTuple c]+      AnyBezier $ V.fromList [from, a, b, c]     _ -> error (show line)--fromPoint :: Point a -> V2 a-fromPoint (Point x y) = V2 x y--toTuple :: V2 a -> (a,a)-toTuple (V2 x y) = (x, y)  unangle :: (Floating a, Ord a) => V2 a -> a unangle a@(V2 ax ay) =
+ examples/doc_augustP.hs view
@@ -0,0 +1,12 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.GeoProjection+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ animate $+  const $ scaleToSize screenWidth screenHeight $+  embedImage $ project smallEarth augustP
+ examples/doc_balloon.hs view
@@ -0,0 +1,14 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+{-# LANGUAGE OverloadedStrings #-}+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Math.Balloon+import Graphics.SvgTree++main :: IO ()+main = reanimate $ docEnv $ pauseAtEnd 1 $ mkAnimation 5 $ \t ->+  withStrokeLineJoin JoinRound $+  balloon (scale 8 $ center $ latex "X") t
+ examples/doc_cameraAttach.hs view
@@ -0,0 +1,20 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Scene+import Control.Lens++main :: IO ()+main = reanimate $ docEnv $ sceneAnimation $ do+  cam <- newObject Camera+  circ <- newObject $ Circle 2+  oModifyS circ $+    oContext .= withFillOpacity 1 . withFillColor "blue"+  oShow circ++  cameraAttach cam circ+  cameraZoom cam 2 2+  cameraZoom cam 2 1
+ examples/doc_cameraFocus.hs view
@@ -0,0 +1,25 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Scene+import Control.Lens++main :: IO ()+main = reanimate $ docEnv $ sceneAnimation $ do+  cam <- newObject Camera+  circ <- newObject $ Circle 2; oShow circ+  oModify circ $ oTranslate .~ (-3,0)+  box <- newObject $ Rectangle 4 4; oShow box+  oModify box $ oTranslate .~ (3,0)++  cameraAttach cam circ+  cameraAttach cam box+  cameraFocus cam (-3,0)+  cameraZoom cam 2 2+  cameraZoom cam 2 1+  cameraFocus cam (3,0)+  cameraZoom cam 2 2+  cameraZoom cam 2 1
+ examples/doc_cassiniP.hs view
@@ -0,0 +1,12 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.GeoProjection+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ animate $+  const $ scaleToSize screenWidth screenHeight $+  embedImage $ project smallEarth cassiniP
+ examples/doc_cylindricalEqualAreaP.hs view
@@ -0,0 +1,12 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.GeoProjection+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ animate $+  const $ scaleToSize screenWidth screenHeight $+  embedImage $ project smallEarth (cylindricalEqualAreaP 0)
+ examples/doc_foucautP.hs view
@@ -0,0 +1,12 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.GeoProjection+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ animate $+  const $ scaleToSize screenWidth screenHeight $+  embedImage $ project smallEarth foucautP
+ examples/doc_githubWhiteIcon.hs view
@@ -0,0 +1,11 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ staticFrame 1 $ mkGroup+  [ mkBackground "grey"+  , githubWhiteIcon ]
+ examples/doc_lagrangeP.hs view
@@ -0,0 +1,12 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main (main) where++import           Reanimate+import           Reanimate.GeoProjection+import           Reanimate.Builtin.Images++main :: IO ()+main = reanimate $ animate $+  const $ scaleToSize screenWidth screenHeight $+  embedImage $ project smallEarth lagrangeP
+ examples/doc_linear.hs view
@@ -0,0 +1,17 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+{-# LANGUAGE OverloadedStrings #-}+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Morph.Common+import Reanimate.Morph.Linear+import Graphics.SvgTree++main :: IO ()+main = reanimate $ docEnv $ playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+  withStrokeLineJoin JoinRound $+  let src = scale 8 $ center $ latex "X"+      dst = scale 8 $ center $ latex "H"+  in morph linear src dst t
+ examples/doc_rawLinear.hs view
@@ -0,0 +1,17 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+{-# LANGUAGE OverloadedStrings #-}+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Morph.Common+import Reanimate.Morph.Linear+import Graphics.SvgTree++main :: IO ()+main = reanimate $ docEnv $ playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+  withStrokeLineJoin JoinRound $+  let src = scale 8 $ center $ latex "X"+      dst = scale 8 $ center $ latex "H"+  in morph rawLinear src dst t
+ examples/doc_rotationalTrajectory.hs view
@@ -0,0 +1,18 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+{-# LANGUAGE OverloadedStrings #-}+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Morph.Common+import Reanimate.Morph.Linear+import Reanimate.Morph.Rotational+import Graphics.SvgTree++main :: IO ()+main = reanimate $ docEnv $ playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+  withStrokeLineJoin JoinRound $+  let src = scale 8 $ center $ latex "X"+      dst = scale 8 $ center $ latex "H"+  in morph linear{morphTrajectory=rotationalTrajectory (0.5,0.5)} src dst t
+ examples/doc_slideDownT.golden view
@@ -0,0 +1,10 @@+0<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 0)"><path d="M-3,0 C-3,0 -3,0 -3,0" /></g><g transform="translate(0, 0)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0 -3,0 -3,0" /></g></g></g></g></g></g></g></g></g></svg>+1<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -0.5)"><path d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g><g transform="translate(0, -0.5)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g></g></g></g></g></g></g></g></g></svg>+2<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -2)"><path d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g><g transform="translate(0, -2)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g></g></g></g></g></g></g></g></g></svg>+3<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -4.5)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g><g transform="translate(0, -4.5)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g></g></g></g></g></g></g></g></g></svg>+4<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -7)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g><g transform="translate(0, -7)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g></g></g></g></g></g></g></g></g></svg>+5<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -8.5)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g><g transform="translate(0, -8.5)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g></g></g></g></g></g></g></g></g></svg>+6<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, -9)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+7<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, -9)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+8<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, -9)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+9<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, -9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, -9)"><g transform="translate(0, 9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>
+ examples/doc_slideDownT.hs view
@@ -0,0 +1,15 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Builtin.Slide+import Reanimate.Transition++main :: IO ()+main = reanimate $ docEnv $ pauseAtEnd 1 $ signalT (curveS 2) slideDownT left right+  where+    left = drawCircle+    right = staticFrame 1 (withFillOpacity 1 $ mkBackground "black") `parA`+            mapA (flipXAxis . withStrokeColor "white") drawCircle
examples/doc_slideLeftT.golden view
@@ -1,10 +1,10 @@-0<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(0, 0)"><path d="M-3,0 C-3,0 -3,0 -3,0" /></g><g transform="translate(0, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0 -3,0 -3,0" /></g></g></g></g></g></g></g></g></svg>-1<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-0.395062, 0)"><path d="M-3,0 C-3,0.16364 -2.986898,0.32422 -2.961686,0.480748" /></g><g transform="translate(-0.395062, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0.16364 -2.986898,0.32422 -2.961686,0.480748" /></g></g></g></g></g></g></g></g></svg>-2<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-1.580247, 0)"><path d="M-3,0 C-3,0.65456 -2.79037,1.260158 -2.434584,1.75332" /></g><g transform="translate(-1.580247, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0.65456 -2.79037,1.260158 -2.434584,1.75332" /></g></g></g></g></g></g></g></g></svg>-3<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-3.555556, 0)"><path d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g><g transform="translate(-3.555556, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g></g></g></g></g></g></g></g></svg>-4<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-6.320988, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C0.961385,3 1.817148,2.547781 2.366177,1.844455" /></g><g transform="translate(-6.320988, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C0.961385,3 1.817148,2.547781 2.366177,1.844455" /></g></g></g></g></g></g></g></g></svg>-5<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-9.679012, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-0.69547 2.763348,-1.335666 2.366177,-1.844455" /></g><g transform="translate(-9.679012, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-0.69547 2.763348,-1.335666 2.366177,-1.844455" /></g></g></g></g></g></g></g></g></svg>-6<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-12.444444, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g><g transform="translate(-12.444444, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g></g></g></g></g></g></g></g></svg>-7<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-14.419753, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.002295,-3 -1.889787,-2.508475 -2.434584,-1.75332" /></g><g transform="translate(-14.419753, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.002295,-3 -1.889787,-2.508475 -2.434584,-1.75332" /></g></g></g></g></g></g></g></g></svg>-8<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-15.604938, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.493214,-3 -2.731627,-1.909065 -2.961686,-0.480748" /></g><g transform="translate(-15.604938, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.493214,-3 -2.731627,-1.909065 -2.961686,-0.480748" /></g></g></g></g></g></g></g></g></svg>-9<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g transform="translate(-16, 0)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(-16, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></svg>+0<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 0)"><path d="M-3,0 C-3,0 -3,0 -3,0" /></g><g transform="translate(0, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0 -3,0 -3,0" /></g></g></g></g></g></g></g></g></g></svg>+1<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-0.888889, 0)"><path d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g><g transform="translate(-0.888889, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g></g></g></g></g></g></g></g></g></svg>+2<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-3.555556, 0)"><path d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g><g transform="translate(-3.555556, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g></g></g></g></g></g></g></g></g></svg>+3<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-8, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g><g transform="translate(-8, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g></g></g></g></g></g></g></g></g></svg>+4<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-12.444444, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g><g transform="translate(-12.444444, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g></g></g></g></g></g></g></g></g></svg>+5<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-15.111111, 0)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g><g transform="translate(-15.111111, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g></g></g></g></g></g></g></g></g></svg>+6<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-16, 0)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(-16, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+7<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-16, 0)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(-16, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+8<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-16, 0)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(-16, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+9<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(-16, 0)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(-16, 0)"><g transform="translate(16, 0)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>
examples/doc_slideLeftT.hs view
@@ -8,7 +8,7 @@ import Reanimate.Transition  main :: IO ()-main = reanimate $ docEnv $ signalT (curveS 2) slideLeftT left right+main = reanimate $ docEnv $ pauseAtEnd 1 $ signalT (curveS 2) slideLeftT left right   where     left = drawCircle     right = staticFrame 1 (withFillOpacity 1 $ mkBackground "black") `parA`
+ examples/doc_slideUpT.golden view
@@ -0,0 +1,10 @@+0<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 0)"><path d="M-3,0 C-3,0 -3,0 -3,0" /></g><g transform="translate(0, 0)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0 -3,0 -3,0" /></g></g></g></g></g></g></g></g></g></svg>+1<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 0.5)"><path d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g><g transform="translate(0, 0.5)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,0.36819 -2.933672,0.720888 -2.812312,1.046797" /></g></g></g></g></g></g></g></g></g></svg>+2<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 2)"><path d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g><g transform="translate(0, 2)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.472759 -1.938749,2.69765 -0.539254,2.951666" /></g></g></g></g></g></g></g></g></g></svg>+3<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 4.5)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g><g transform="translate(0, 4.5)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,0 3,0 3,0" /></g></g></g></g></g></g></g></g></g></svg>+4<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 7)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g><g transform="translate(0, 7)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-0.184095,-3 -0.364317,-2.983418 -0.539254,-2.951666" /></g></g></g></g></g></g></g></g></g></svg>+5<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 8.5)"><path d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g><g transform="translate(0, 8.5)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 C-3,1.656854 -1.656854,3 0,3 C1.656854,3 3,1.656854 3,0 C3,-1.656854 1.656854,-3 0,-3 C-1.288664,-3 -2.387555,-2.18748 -2.812312,-1.046797" /></g></g></g></g></g></g></g></g></g></svg>+6<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, 9)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+7<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, 9)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+8<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, 9)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>+9<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g stroke-width="0" fill="#FFFFFF" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g stroke-width="0.1" stroke="#000000" fill-opacity="0"><g><g><g transform="translate(0, 9)"><path d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g><g transform="translate(0, 9)"><g transform="translate(0, -9)"><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><g transform="scale(-1, 1)"><path stroke="#FFFFFF" d="M-3,0 a3 3 0 1 0 6,0 3 3 0 1 0 -6,0" /></g></g></g></g></g></g></g></g></g></svg>
+ examples/doc_slideUpT.hs view
@@ -0,0 +1,15 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation+import Reanimate.Builtin.Slide+import Reanimate.Transition++main :: IO ()+main = reanimate $ docEnv $ pauseAtEnd 1 $ signalT (curveS 2) slideUpT left right+  where+    left = drawCircle+    right = staticFrame 1 (withFillOpacity 1 $ mkBackground "black") `parA`+            mapA (flipXAxis . withStrokeColor "white") drawCircle
+ examples/doc_spriteScope.hs view
@@ -0,0 +1,19 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+module Main(main) where++import Reanimate+import Reanimate.Builtin.Documentation++main :: IO ()+main = reanimate $ docEnv $ sceneAnimation $ do+  -- the rect lives through the entire 3s animation+  newSpriteSVG_ $ translate (-3) 0 $ mkRect 4 4+  wait 1+  spriteScope $ do+    -- the circle only lives for 1 second.+    local <- newSpriteSVG $ translate 3 0 $ mkCircle 2+    spriteE local $ overBeginning 0.3 fadeInE+    spriteE local $ overEnding 0.3 fadeOutE+    wait 1+  wait 1
examples/doc_ternaryPlot.hs view
@@ -2,13 +2,15 @@ -- stack runghc --package reanimate module Main(main) where -import Reanimate hiding (raster)-import Reanimate.Builtin.Documentation-import Reanimate.Builtin.TernaryPlot-import Reanimate.ColorComponents-import Data.Colour.CIE-import Codec.Picture.Types+import           Codec.Picture.Types+import           Reanimate+import           Reanimate.Builtin.Documentation+import           Reanimate.Builtin.TernaryPlot  main :: IO ()-main = reanimate $ docEnv $ animate $ const $-  ternaryPlot 1024 (\a b c -> promotePixel $ toRGB8 $ cieXYZ a b c)+main = reanimate $ docEnv $ staticFrame 1 $+  ternaryPlot 100 $ \aCoord bCoord cCoord -> promotePixel $+    let red   = round $ aCoord*255+        green = round $ bCoord*255+        blue  = round $ cCoord*255+    in PixelRGB8 red green blue
+ examples/fe_morph.hs view
@@ -0,0 +1,83 @@+#!/usr/bin/env stack+-- stack runghc --package reanimate+{-# LANGUAGE ApplicativeDo     #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ParallelListComp  #-}+module Main(main) where++import           Text.Printf++{- FE articles/demos+scatter: https://codepen.io/mullany/pen/JmgbRB+distortion: https://codepen.io/mullany/pen/BWKePz+variable stroke width: https://codepen.io/mullany/pen/qaONQm+variable stroke gradient: https://codepen.io/mullany/pen/XXBMJd+heart: https://codepen.io/yoksel/pen/MLVjoB+elastic stroke: https://codepen.io/yoksel/pen/XJbzrO+-}++import           Codec.Picture.Types+import           Control.Lens                    hiding (magma)+import           Graphics.SvgTree+import           Reanimate++main :: IO ()+main = reanimate $ sceneAnimation $ do+    newSpriteSVG_ $ mkBackground "white"+    let circles =+          [ (900, 0.3)+          , (760, 0.5)+          , (670, 0.4)+          , (600, 0.6)+          , (450, 0.7) ]+    play $ pauseAtEnd 2 $ mkAnimation 10 $ \t ->+      mkGroup+      [ mkGooeyFilter 0.2 -- fromToS 0.1 0.2 $ oscillateS t+      , mkGroup+        [ lowerTransformations $ center $ withFillColor "black" $ latex "Text"+        , mkGroup+          [rotate (t*rot) $ translate (fromToS 0 3.5 $ bellS 2 $ t) 0 $+            withFillColorPixel (promotePixel c) $ mkCircle (size*1.5)+          | (n,(rot, size)) <- zip [0..] circles+          , let c = turbo (n/fromIntegral (length circles-1))+          ]+        ] & filterRef .~ pure (Ref "gooey")++      ]+    -- wait 1++mkGooeyFilter :: Double -> SVG+mkGooeyFilter blur =+  FilterTree $ mkFilter ("gooey")+      [ FEGaussianBlur $ defaultSvg+        & gaussianBlurStdDeviationX .~ Num blur+        & gaussianBlurEdgeMode      .~ EdgeNone+        & filterResult              .~ Just "blur"+      , FEColorMatrix $ defaultSvg+        & colorMatrixType           .~ Matrix+        & colorMatrixValues         .~ printf+          "1 0 0 0 0 \+          \0 1 0 0 0 \+          \0 0 1 0 0 \+          \0 0 0 %f %f" mul sub+        & colorMatrixIn             .~ pure (SourceRef "blur")+        & filterResult              .~ Just "colormatrix"+      ]+  where+    -- Increase alpha contrast. Turns alphaMin to 0x00 and alphaMax to 0xFF.+    -- Increasing alphaDiff makes the shapes softer. Decreasing it gives more+    -- contrast.+    -- alphaMin*mul + sub*255 = 0+    -- alphaMax*mul + sub*255 = 255+    -- alphaDiff*mul = 255+    -- mul = 255/alphaDiff+    -- sub = -alphaMin*mul/255+    mul = 255/alphaDiff+    sub = -alphaMin*mul/255+    alphaCenter = 255/2 :: Double+    alphaDiff   = 20 :: Double+    alphaMin = alphaCenter - alphaDiff/2+    -- alphaMax = alphaCenter + alphaDiff/2++mkFilter :: String -> [FilterElement] -> Filter+mkFilter ident fe = defaultSvg & filterChildren .~ fe & attrId ?~ ident
examples/intro_canvas.hs view
@@ -6,28 +6,22 @@   ) where -import           Reanimate-import           Reanimate.Builtin.Documentation+import           Data.Text                       (Text)+import qualified Data.Text                       as T+import           Graphics.SvgTree                hiding (Point, Text, height)+import           Linear.Metric import           Linear.V2 import           Linear.Vector-import           Linear.Metric-import           Graphics.SvgTree                  hiding ( Text-                                                          , Point-                                                          , height-                                                          )-import           Data.Text                                ( Text )-import qualified Data.Text                     as T+import           Reanimate+import           Reanimate.Builtin.Documentation+import           Geom2D.CubicBezier.Linear import           Text.Printf-import           Geom2D.CubicBezier                       ( QuadBezier(..)-                                                          , evalBezier-                                                          , Point(..)-                                                          )  main :: IO () main = reanimate $ sceneAnimation $ do   newSpriteSVG_ $ mkBackgroundPixel rtfdBackgroundColor   newSpriteSVG_ static-  dotPath  <- newVar (QuadBezier (Point 0 0) (Point 0 0) (Point 0 0))+  dotPath  <- newVar (QuadBezier (V2 0 0) (V2 0 0) (V2 0 0))   dotParam <- newVar 0   newSprite_ $ redDot <$> (evalBezier <$> unVar dotPath <*> unVar dotParam)   let moveDot a b = do@@ -37,15 +31,15 @@         tweenVar dotParam 5 $ \v -> fromToS v 1 . curveS 2         wait 1   wait 1-  moveDot (Point 0 1)    (Point 4 2)-  moveDot (Point (-1) 0) (Point (-4) 3)-  moveDot (Point (0) 0)  (Point (-4) (-3))-  moveDot (Point (0) 0)  (Point (5) (-2))-  moveDot (Point (6) 2)  (Point 2 1)-  moveDot (Point (0) 0)  (Point 0 0)+  moveDot (V2 0 1)    (V2 4 2)+  moveDot (V2 (-1) 0) (V2 (-4) 3)+  moveDot (V2 (0) 0)  (V2 (-4) (-3))+  moveDot (V2 (0) 0)  (V2 (5) (-2))+  moveDot (V2 (6) 2)  (V2 2 1)+  moveDot (V2 (0) 0)  (V2 0 0) -redDot :: Point Double -> SVG-redDot (Point x y) = translate x y $ mkGroup+redDot :: V2 Double -> SVG+redDot (V2 x y) = translate x y $ mkGroup   [ translate 0 (-0.5) $ scale 0.5 $ outlinedText $ T.pack $ printf "%.1f,%.1f" x y   , withFillColor "red" $ mkCircle 0.1   ]
examples/intro_canvas_square.hs view
@@ -6,22 +6,16 @@   ) where -import           Reanimate-import           Reanimate.Builtin.Documentation+import           Data.Text                       (Text)+import qualified Data.Text                       as T+import           Graphics.SvgTree                hiding (Text, height)+import           Linear.Metric import           Linear.V2 import           Linear.Vector-import           Linear.Metric-import           Graphics.SvgTree                  hiding ( Text-                                                          , Point-                                                          , height-                                                          )-import           Data.Text                                ( Text )-import qualified Data.Text                     as T+import           Reanimate+import           Reanimate.Builtin.Documentation+import           Geom2D.CubicBezier.Linear import           Text.Printf-import           Geom2D.CubicBezier                       ( QuadBezier(..)-                                                          , evalBezier-                                                          , Point(..)-                                                          )  newWidth, newHeight :: Double newWidth = 8@@ -31,7 +25,7 @@ main = reanimate $ mapA squareViewBox $ sceneAnimation $ do   newSpriteSVG_ $ mkBackgroundPixel rtfdBackgroundColor   newSpriteSVG_ static-  dotPath  <- newVar (QuadBezier (Point 0 0) (Point 0 0) (Point 0 0))+  dotPath  <- newVar (QuadBezier (V2 0 0) (V2 0 0) (V2 0 0))   dotParam <- newVar 0   newSprite_ $ redDot <$> (evalBezier <$> unVar dotPath <*> unVar dotParam)   let moveDot a b = do@@ -40,18 +34,18 @@         writeVar dotParam 0         tweenVar dotParam 5 $ \v -> fromToS v 1 . curveS 2         wait 1-  moveDot (Point 0 1)     (Point 2 1)-  moveDot (Point (-1) 0)  (Point (-1) 1.5)-  moveDot (Point (0) 0)   (Point (-2) (-3))-  moveDot (Point (0) 0)   (Point (2.5) (-1))-  moveDot (Point (3) 1.5) (Point 1.5 1)-  moveDot (Point (0) 0)   (Point 0 0)+  moveDot (V2 0 1)     (V2 2 1)+  moveDot (V2 (-1) 0)  (V2 (-1) 1.5)+  moveDot (V2 (0) 0)   (V2 (-2) (-3))+  moveDot (V2 (0) 0)   (V2 (2.5) (-1))+  moveDot (V2 (3) 1.5) (V2 1.5 1)+  moveDot (V2 (0) 0)   (V2 0 0)  squareViewBox :: SVG -> SVG squareViewBox = withViewBox (-4, -4, 8, 8) -redDot :: Point Double -> SVG-redDot (Point x y) = translate x y $ mkGroup+redDot :: V2 Double -> SVG+redDot (V2 x y) = translate x y $ mkGroup   [ translate 0 (-0.5) $ scale 0.5 $ outlinedText $ T.pack $ printf "%.1f,%.1f" x y   , withFillColor "red" $ mkCircle 0.1   ]
examples/latex_wheel.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE RecursiveDo       #-} module Main (main) where +import           Control.Lens     ((&)) import           Control.Monad    (forM_) import           Graphics.SvgTree (Tree) import           Reanimate@@ -16,28 +17,28 @@ mainScene :: Animation mainScene = sceneAnimation $ mdo     play $ drawCircle-      # setDuration drawCircleT-      # applyE (constE $ scaleXY (-1) 1)+      & setDuration drawCircleT+      & applyE (constE $ scaleXY (-1) 1)     fork $ play $ drawCircle-      # freezeAtPercentage 1-      # setDuration rotDur+      & freezeAtPercentage 1+      & setDuration rotDur     rotDur <- withSceneDuration $ waitOn $       forM_ svgs $ \svg -> do         fork $ play $ drawTick-          # setDuration rotateT-          # repeatA rotateN-          # applyE (overBeginning 0.5 drawInE)-          # applyE (overEnding 0.5 drawOutE)+          & setDuration rotateT+          & repeatA rotateN+          & applyE (overBeginning 0.5 drawInE)+          & applyE (overEnding 0.5 drawOutE)         fork $ play $ drawSVG svg-          # setDuration rotateT-          # repeatA rotateN-          # applyE (overBeginning rotateT drawInE)-          # applyE (delayE rotateT $ overBeginning 1 fillInE)-          # applyE (overEnding 0.5 fadeOutE)+          & setDuration rotateT+          & repeatA rotateN+          & applyE (overBeginning rotateT drawInE)+          & applyE (delayE rotateT $ overBeginning 1 fillInE)+          & applyE (overEnding 0.5 fadeOutE)         wait (rotateT / fromIntegral (1+length svgs))     play $ drawCircle-      # setDuration drawCircleT-      # reverseA+      & setDuration drawCircleT+      & reverseA     return ()   where     drawCircleT = 1
examples/morphology_closest.hs view
@@ -6,8 +6,9 @@  import           Codec.Picture import           Codec.Picture.Types+import           Control.Lens           ((&)) import           Control.Monad-import           Graphics.SvgTree         (LineJoin (..))+import           Graphics.SvgTree       (LineJoin (..)) import           Reanimate import           Reanimate.Morph.Common import           Reanimate.Morph.Linear@@ -34,11 +35,11 @@     showPair from to =       waitOn $ do         fork $ play $ mkAnimation 4 (morph rawLinear from to)-          # mapA (translate (-4) (-0.5))-          # signalA (curveS 4)+          & mapA (translate (-4) (-0.5))+          & signalA (curveS 4)         fork $ play $ mkAnimation 4 (morph linear from to)-          # mapA (translate (4) (-0.5))-          # signalA (curveS 4)+          & mapA (translate (4) (-0.5))+          & signalA (curveS 4)      pairs = zip stages (tail stages ++ [head stages])     stages = map (lowerTransformations . scale 8 . pathify . center) $ colorize
examples/morphology_intro.hs view
@@ -5,6 +5,7 @@ module Main(main) where  import           Codec.Picture+import           Control.Lens           ((&)) import           Reanimate import           Reanimate.Morph.Common import           Reanimate.Morph.Linear@@ -23,7 +24,7 @@       play $ step stage2 stage3       play $ step stage3 stage4       play $ step stage4 stage1-        # setDuration 5+        & setDuration 5   where     radius = 2.5     step from to =
− examples/morphology_leastwork.hs
@@ -1,69 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ParallelListComp  #-}-module Main(main) where--import           Codec.Picture-import           Codec.Picture.Types-import           Control.Monad-import           Graphics.SvgTree          (LineJoin (..))-import           Reanimate-import           Reanimate.Morph.Common-import           Reanimate.Morph.LeastWork-import           Reanimate.Morph.Linear--bgColor :: PixelRGBA8-bgColor = PixelRGBA8 252 252 252 0xFF--main :: IO ()-main = reanimate $-  addStatic (mkBackgroundPixel bgColor) $-  mapA (withStrokeWidth defaultStrokeWidth) $-  mapA (withStrokeColor "black") $-  mapA (withStrokeLineJoin JoinRound) $-  mapA (withFillOpacity 1) $-    sceneAnimation $ do-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (-3) 4 $-        center $ latex "linear"-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (3) 4 $-        center $ latex "least-work"-      forM_ pairs $ uncurry showPair-  where-    showPair from to =-      waitOn $ do-        fork $ play $ mkAnimation 4 (morph linear from to)-          # mapA (translate (-3) (-0.5))-          # signalA (curveS 4)-        fork $ play $ mkAnimation 4 (morph myMorph from to)-          # mapA (translate (3) (-0.5))-          # signalA (curveS 4)--    stretchCosts = defaultStretchCosts-      { stretchStiffness = 2 }-    bendCosts = defaultBendCosts-    myMorph = linear{morphPointCorrespondence = leastWork stretchCosts bendCosts }-    pairs = zip stages (tail stages ++ [head stages])-    stages = map (lowerTransformations . scale 6 . pathify . center) $ colorize-      [ latex "X"-      , latex "$\\aleph$"-      , latex "Y"-      , latex "$\\infty$"-      , latex "I"-      , latex "$\\pi$"-      , latex "1"-      , latex "T"-      , latex "I"-      , latex "L"-      , latex "S"-      , mkRect 0.5 0.5-      ]--colorize :: [SVG] -> [SVG]-colorize lst =-  [ withFillColorPixel (promotePixel $ parula (n/fromIntegral (length lst-1))) elt-  | elt <- lst-  | n <- [0..]-  ]
examples/morphology_linear.hs view
@@ -6,7 +6,8 @@  import           Codec.Picture import           Codec.Picture.Types-import           Graphics.SvgTree         (LineJoin (..))+import           Control.Lens           ((&))+import           Graphics.SvgTree       (LineJoin (..)) import           Reanimate import           Reanimate.Morph.Common import           Reanimate.Morph.Linear@@ -31,8 +32,8 @@         spriteE toS $ overBeginning 0.2 fadeInE         spriteE toS $ overEnding 0.2 fadeOutE         m <- fork $ newSpriteA $ animate (morph linear from to)-          # signalA (curveS 4)-          # mapA (translate (-4) 0)+          & signalA (curveS 4)+          & mapA (translate (-4) 0)         wait 4         destroySprite m         destroySprite toS
− examples/morphology_linebend.hs
@@ -1,63 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ParallelListComp  #-}-module Main(main) where--import           Codec.Picture-import           Codec.Picture.Types-import           Control.Monad-import           Graphics.SvgTree         (LineJoin (..))-import           Reanimate-import           Reanimate.Morph.Common-import           Reanimate.Morph.Linear-import           Reanimate.Morph.LineBend--bgColor :: PixelRGBA8-bgColor = PixelRGBA8 252 252 252 0xFF--main :: IO ()-main = reanimate $-  addStatic (mkBackgroundPixel bgColor) $-  mapA (withStrokeWidth defaultStrokeWidth) $-  mapA (withStrokeColor "black") $-  mapA (withStrokeLineJoin JoinRound) $-  mapA (withFillOpacity 1) $-    sceneAnimation $ do-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (-3) 4 $-        center $ latex "linear"-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (3) 4 $-        center $ latex "line bend"-      forM_ pairs $ uncurry showPair-  where-    showPair from to =-      waitOn $ do-        fork $ play $ mkAnimation 4 (morph linear from to)-          # mapA (translate (-3) (-0.5))-          # signalA (curveS 4)-        fork $ play $ mkAnimation 4 (morph myMorph from to)-          # mapA (translate (3) (-0.5))-          # signalA (curveS 4)--    myMorph = linear{morphTrajectory = lineBend }-    pairs = zip stages (tail stages ++ [head stages])-    stages = map (lowerTransformations . scale 6 . pathify . center) $ colorize-      [ latex "X"-      , latex "$\\aleph$"-      , latex "Y"-      , latex "$\\infty$"-      , latex "I"-      , latex "$\\pi$"-      , latex "1"-      , latex "S"-      , mkRect 0.5 0.5-      ]--colorize :: [SVG] -> [SVG]-colorize lst =-  [ withFillColorPixel (promotePixel $ parula (n/fromIntegral (length lst-1))) elt-  | elt <- lst-  | n <- [0..]-  ]
− examples/morphology_linebend_intro.hs
@@ -1,100 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ParallelListComp  #-}-module Main(main) where--import           Codec.Picture-import           Control.Monad-import           Data.List                (transpose)-import qualified Data.Vector              as V-import           Graphics.SvgTree         (LineJoin (..))-import           Linear.V2-import           Reanimate-import           Reanimate.Math.Polygon-import           Reanimate.Morph.Common-import           Reanimate.Morph.Linear-import           Reanimate.Morph.LineBend--bgColor :: PixelRGBA8-bgColor = PixelRGBA8 252 252 252 0xFF--spike1 :: Polygon-spike1 = pScale 2.5 $ mkPolygon $ V.map (\x -> x - V2 1 1) $ V.fromList-  [ V2 0 0, V2 2 0-  , V2 2 1 ]--spike2 :: Polygon-spike2 = pScale 2.5 $ mkPolygon $ V.map (\x -> x - V2 1 1) $ V.fromList-  [ V2 0 0, V2 2 0-  , V2 0 1 ]--spike3 :: Polygon-spike3 = pScale 2.5 $ mkPolygon $ V.map (\x -> x - V2 1 1) $ V.fromList-  [ V2 0.5 0, V2 1.5 0-  , V2 1 2 ]--polygonShape :: Polygon -> SVG-polygonShape p = mkLinePathClosed-  [ (x,y) | V2 x y <- map (fmap realToFrac) $ V.toList (polygonPoints p) ]---main :: IO ()-main = reanimate $-  addStatic (mkBackgroundPixel bgColor) $-  mapA (withStrokeWidth defaultStrokeWidth) $-  mapA (withStrokeColor "black") $-  mapA (withStrokeLineJoin JoinRound) $-  mapA (withFillOpacity 1) $-    sceneAnimation $ do-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (-4) 4 $-        center $ latex "linear"-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (4) 4 $-        center $ latex "line bend"-      showTrails-      forM_ pairs $ uncurry showPair-  where-    showTrails = do-      _ <- newSpriteSVG $-            translate (-4) (-0.5) $-            genTrails (map linearTrajectory pairs)-      _ <- newSpriteSVG $-            translate (4) (-0.5) $-            genTrails (map lineBend pairs)-      return ()-    showPair from to =-      waitOn $ do-        fork $ play $ mkAnimation 4 (morph linear (polygonShape from) (polygonShape to))-          # mapA (translate (-4) (-0.5))-          # mapA (withFillColor "lightgreen")-          # signalA (curveS 4)-        fork $ play $ mkAnimation 4 (morph myMorph (polygonShape from) (polygonShape to))-          # mapA (translate (4) (-0.5))-          # mapA (withFillColor "cyan")-          # signalA (curveS 4)-    myMorph = linear{morphTrajectory = lineBend }-    pairs = zip stages (tail stages ++ [head stages])-    stages =-      [ spike1-      , spike2-      , spike3-      ]--genTrails :: [(Double -> Polygon)] -> SVG-genTrails plotters =-    withFillOpacity 0 $-    withStrokeWidth (defaultStrokeWidth*0.5) $-    withStrokeColor "black" $-    withStrokeDashArray [0.1,0.05] $-    mkGroup $ map mkTrail $ transpose $ concat-      [-        [ V.toList $ V.map (fmap realToFrac) (polygonPoints poly)-        | n <- [0..steps]-        , let poly = plotter (fromIntegral n / fromIntegral steps)-        ]-      | plotter <- plotters ]-  where-    steps = 100 :: Int-    mkTrail lst = mkLinePath [ (x,y) | V2 x y <- lst ]
examples/morphology_object_correspondence.hs view
@@ -5,6 +5,7 @@ module Main(main) where  import           Codec.Picture+import           Control.Lens           ((&)) import           Reanimate import           Reanimate.Morph.Common import           Reanimate.Morph.Linear@@ -21,9 +22,9 @@     sceneAnimation $ do       let showLabel label = do             fork $ play $ staticFrame 4 (center $ latex label)-              # mapA (translate 0 4)-              # applyE (overBeginning 0.1 fadeInE)-              # applyE (overEnding 0.1 fadeOutE)+              & mapA (translate 0 4)+              & applyE (overBeginning 0.1 fadeInE)+              & applyE (overEnding 0.1 fadeOutE)       let sides = mkGroup             [ translate (-4) 0 $ withFillColor "blue" $               mkCircle 2@@ -35,21 +36,21 @@        showLabel "Cut"       play $ step sides middle-        # pauseAtEnd 1+        & pauseAtEnd 1       play $ step middle sides-        # pauseAtEnd 1+        & pauseAtEnd 1        showLabel "Overlap"       play $ stepDup sides middle-        # pauseAtEnd 1+        & pauseAtEnd 1       play $ stepDup middle sides-        # pauseAtEnd 1+        & pauseAtEnd 1        showLabel "Obliterate"       play $ stepGenesis sides middle-        # pauseAtEnd 1+        & pauseAtEnd 1       play $ stepGenesis middle sides-        # pauseAtEnd 1+        & pauseAtEnd 1   where     step from to =       signalA (curveS 2) $ animate $ morph linear from to
examples/morphology_point_correspondence.hs view
@@ -6,9 +6,10 @@  import           Codec.Picture import           Codec.Picture.Types-import           Control.Monad                   (replicateM_)-import qualified Data.Text                       as T-import qualified Data.Vector                     as V+import           Control.Lens           ((&))+import           Control.Monad          (replicateM_)+import qualified Data.Text              as T+import qualified Data.Vector            as V import           Linear.V2 import           Linear.Vector import           Reanimate@@ -52,7 +53,7 @@       replicateM_ 8 $ do         offsetVal <- readVar offset         play $ step pl1 (pl2 offsetVal 0)-          # setDuration 3+          & setDuration 3         slideLeft   where     step from to =
− examples/morphology_point_trajectory.hs
@@ -1,320 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-module Main where--import           Codec.Picture.Types-import           Control.Lens-import           Control.Monad-import           Data.List-import           Data.Maybe-import qualified Data.Text                     as T-import           Data.Tuple-import qualified Data.Vector                   as V-import           Linear.V2-import           Linear.Vector-import qualified Numeric.LinearAlgebra         as Matrix-import           Numeric.LinearAlgebra.HMatrix (Matrix, linearSolve, toLists,-                                                (><))-import           Reanimate-import           Reanimate.Math.Common-import           Reanimate.Math.Polygon--bgColor :: PixelRGBA8-bgColor = PixelRGBA8 252 252 252 0xFF--type Points = V.Vector (V2 Double)-type Edges = [(Int, Int, Int)]-data Mesh = Mesh { meshPoints :: Points, meshEdges :: Edges }-data MeshPair = MeshPair Points Points Edges--- The points in a RelMesh are:---   relMeshStatic ++ x where Ax = B-data RelMesh = RelMesh-  { relMeshStatic :: Points-  , relMeshEdges  :: Edges-  , relMeshA      :: Matrix Double-  , relMeshB      :: Matrix Double-  }-data RelMeshPair = RelMeshPair Points Edges (Matrix Double) (Matrix Double) (Matrix Double) (Matrix Double)--- Linear interpolation on RelMesh gives smooth morph.--- solveMesh :: RelMesh -> Mesh--- mkRelative :: Mesh -> RelMesh--- mkRelativePair :: MeshPair -> RelMeshPair--- triangulate :: Polygon -> Polygon -> MeshPair ?--- embed :: MeshPair -> MeshPair--- compatible :: Mesh -> Mesh -> Maybe MeshPair--- linearInterpolate :: MeshPair -> Double -> Mesh--- convexInterpolate :: RelMeshPair -> Double -> RelMesh--meshToPolygon :: Mesh -> Polygon-meshToPolygon mesh = pScale 2 $ mkPolygon $ V.fromList-    [ realToFrac <$> (meshPoints mesh V.! (n-1))-    | n <- polygonNodes ]-  where-    polygonNodes :: [Int]-    polygonNodes = [5,8,9,7,4,6]--main :: IO ()-main = reanimate morphAnimation--genTrails :: (Double -> Mesh) -> SVG-genTrails mkMesh =-    withFillOpacity 0 $-    withStrokeWidth (defaultStrokeWidth*0.5) $-    withStrokeColor "black" $-    withStrokeDashArray [0.1,0.1] $-    mkGroup $ map mkTrail $ transpose-      [ V.toList $ V.map (fmap realToFrac) $ polygonPoints $ meshToPolygon mesh-      | n <- [0..steps]-      , let mesh = mkMesh (fromIntegral n / fromIntegral steps)-      ]-  where-    steps = 100 :: Int-    mkTrail lst =mkLinePath [ (x,y) | V2 x y <- lst ]--morphAnimation :: Animation-morphAnimation = playThenReverseA $ pauseAround 1 1 $ addStatic (mkBackgroundPixel bgColor) $-  signalA (curveS 2) $ mkAnimation 5 $ \t -> lowerTransformations $ scale 3 $ pathify $-  mkGroup-  [ translate (-1.5) 0 $ withFillColor "lightgreen" $ mkGroup-    [ withStrokeWidth defaultStrokeWidth $-      withStrokeColor "black" $-      polygonShape $ meshToPolygon $ linearInterpolate meshPair t-    , linearTrails-    , polygonNumDots (meshToPolygon $ linearInterpolate meshPair t) 0-    ]-  , translate 1 0 $ withFillColor "cyan" $ mkGroup-    [ withStrokeWidth defaultStrokeWidth $-      withStrokeColor "black" $-      polygonShape $ meshToPolygon $ solveMesh $ convexInterpolate relPair t-    , curveTrails-    , polygonNumDots (meshToPolygon $ solveMesh $ convexInterpolate relPair t) 0-    ]-  ]-  where-    linearTrails = genTrails (linearInterpolate meshPair)-    curveTrails = genTrails (solveMesh . convexInterpolate relPair)-    relPair = mkRelativePair meshPair-    meshPair = fromJust $ compatible example1 example2--mkLineP :: P -> P -> SVG-mkLineP (V2 x1 y1) (V2 x2 y2) = mkLine (x1,y1) (x2,y2)---- FIXME: Check that the triangles are all anticlockwise.--- FIXME: Check that the edges connect all the points.--- FIXME: Check that the edgse leave no gaps.-compatible :: Mesh -> Mesh -> Maybe MeshPair-compatible a b =-  if meshEdges a == meshEdges b && V.length (meshPoints a) == V.length (meshPoints b)-    then Just $ MeshPair (meshPoints a) (meshPoints b) (meshEdges a)-    else Nothing--linearInterpolate :: MeshPair -> Double -> Mesh-linearInterpolate (MeshPair aP bP edges) t = Mesh-    { meshPoints = V.zipWith (lerp (1-t)) aP bP-    , meshEdges = edges }--example1 :: Mesh-example1 = Mesh points edges-  where-    points = V.fromList-      [ V2 1 0-      , V2 (-1/2) (sqrt 3 / 2)-      , V2 (-1/2) (-sqrt 3 / 2)-      , 3 * points V.! 0 ^/ 4-      , 3 * points V.! 1 ^/ 4-      , 3 * points V.! 2 ^/ 4-      , points V.! 0 ^/ 2-      , points V.! 1 ^/ 2-      , points V.! 2 ^/ 2-      ]-    edges =-      [ (1,5,4), (1,2,5), (2,6,5), (2,3,6), (3,4,6), (3,1,4)-      , (4,8,7), (4,5,8), (5,9,8), (5,6,9), (6,7,9), (6,4,7), (7,8,9)]--example2 :: Mesh-example2 = Mesh points edges-  where-    points = V.fromList-      [ V2 1 0-      , V2 (-1/2) (sqrt 3 / 2)-      , V2 (-1/2) (-sqrt 3 / 2)-      , 3 * points V.! 2 ^/ 4-      , 3 * points V.! 0 ^/ 4-      , 3 * points V.! 1 ^/ 4-      , points V.! 1  ^/ 2-      , points V.! 2 ^/ 2-      , points V.! 0 ^/ 2-      ]-    edges = meshEdges example1---- T = (U, G)--- G = [Polygon]--- U = nub $ concat G--findStarNeighbours :: Eq a => [(a,a,a)] -> a -> [(a, a)]-findStarNeighbours allTrig self =-  [ (b,c)-  | (a,b,c) <- allTrig-  , self == a-  ] ++-  [ (c,a)-  | (a,b,c) <- allTrig-  , self == b-  ] ++-  [ (a,b)-  | (a,b,c) <- allTrig-  , self == c-  ]--isInterior :: Eq a => [(a, a)] -> Bool-isInterior = isJust . getExteriorPoly--getExteriorPoly :: Eq a => [(a, a)] -> Maybe [a]-getExteriorPoly [] = Nothing-getExteriorPoly ((a,b):rest) = worker [a] a b rest-  where-    worker acc start this [] = do-      guard (start == this)-      return (reverse acc)-    worker acc start this xs =-      case lookup this xs of-        Just next -> worker (this:acc) start next (delete (this,next) xs)-        Nothing   ->-          case lookup this (map swap xs) of-            Just next -> worker (this:acc) start next (delete (next, this) xs)-            Nothing   -> Nothing--convexInterpolate :: RelMeshPair -> Double -> RelMesh-convexInterpolate (RelMeshPair static edges leftM leftB rightM rightB) t =-  RelMesh-  { relMeshStatic = static-  , relMeshEdges  = edges-  , relMeshA      = Matrix.scale (1-t) leftM +-                    Matrix.scale t rightM-  , relMeshB      = Matrix.scale (1-t) leftB +-                    Matrix.scale t rightB-  }--solveMesh :: RelMesh -> Mesh-solveMesh (RelMesh static edges m b) =-  case linearSolve m b of-    Nothing -> error "Failed to solve mesh"-    Just ret ->-      Mesh (static <> V.fromList (worker (toLists ret))) edges-  where-    worker []             = []-    worker ([x]:[y]:rest) = V2 x y : worker rest-    worker _              = error "invalid result"--mkRelative :: Mesh -> RelMesh-mkRelative (Mesh points edges) = RelMesh (V.fromList exteriorPoints) edges mM bM-  where-    mM = (s><s) (concat m)-    bM = (s><1) b-    (s,exterior, (m, b)) = toParameters points edges-    exteriorPoints =-      [ points V.! (i-1)-      | i <- exterior-      ]--mkRelativePair :: MeshPair -> RelMeshPair-mkRelativePair (MeshPair p1 p2 edges) =-  let RelMesh static _ leftM leftB = mkRelative (Mesh p1 edges)-      RelMesh _ _ rightM rightB = mkRelative (Mesh p2 edges)-  in RelMeshPair static edges leftM leftB rightM rightB--toParameters :: (Ord b, Fractional b) => V.Vector (V2 b) -> [(Int, Int, Int)] -> (Int, [Int], ([[b]], [b]))-toParameters points groups = (length interior*2,exterior,unzip $ concat-  [ let lst = [(if i == j then -1 else t)-              | j <- interior-              , let t = fromMaybe 0 $ lookup (i,j) lam_ij_cache-              ]-        pos = negate $ sum-           [ pj ^* t-           | j <- exterior-           , let t = fromMaybe 0 $ lookup (i,j) lam_ij_cache-                 pj = points V.! (j-1)-           ]-    in [ (dupX lst, pos ^. _x)-       , (dupY lst, pos ^. _y)]-  | i <- interior ])-  where-    lam_ij_cache = lam_ij points groups-    dupX []     = []-    dupX (x:xs) = x:0:dupX xs-    dupY []     = []-    dupY (x:xs) = 0:x:dupY xs-    (interior, exterior) =-      partition (isInterior . findStarNeighbours groups) [1 .. length points]--lam_ij :: (Ord b, Fractional b) => V.Vector (V2 b) -> [(Int, Int, Int)] -> [((Int, Int), b)]-lam_ij points groups =-  [ ((i, j), t)-  | i <- [1..V.length points]-  , (j, t) <- lam_j points groups i-  ]--lam_j :: (Ord b, Fractional b) => V.Vector (V2 b) -> [(Int, Int, Int)] -> Int -> [(Int, b)]-lam_j points groups p =-  [ (nP, sum [ t | (j,_k,t) <- mu, j == nP ] / fromIntegral (length nPoints))-  | let n = findStarNeighbours groups p-        nPoints = fromMaybe [] $ getExteriorPoly n-        mu = calcMu points groups p-  , nP <- nPoints ]--calcMu :: (Ord c, Fractional c) => V.Vector (V2 c) -> [(Int, Int, Int)] -> Int -> [(Int, Int, c)]-calcMu points groups p = concat-    [ [ (nP, nP, t1)-      , (a, nP, t2)-      , (b, nP, t3) ]-      -- (nP, a, b)-    | {-p <- [1..length points]-}-      let selfVert = points V.! (p-1)-          n = findStarNeighbours groups p-          nPoints :: [Int]-          nPoints = fromMaybe [] $ getExteriorPoly n-    , nP <- nPoints-    , let vert = points V.! (nP-1)-    , let line = (vert, selfVert)-    , let (a,b,aP,bP) = head $-            [ (_a,_b,_aP,_bP)-            | (_a,_b) <- n-            , let _aP = points V.! (_a-1)-                  _bP = points V.! (_b-1)-                  segment = (points V.! (_a-1), points V.! (_b-1))-            , case rayIntersect line segment of-                Nothing -> False-                Just u  -> isBetween u segment-            , _a /= nP-            , _b /= nP ]-    -- , b == (nPoints ++ nPoints) !! i-    , let (t1,t2,t3) = barycentricCoords vert aP bP selfVert-    ]--genColor :: Int -> Int -> PixelRGBA8-genColor n m =-    promotePixel $ parula (fromIntegral (n+offset) / fromIntegral (m+offset))-  where-    offset = 5--polygonNumDots :: Polygon -> Double -> SVG-polygonNumDots p t = mkGroup $ reverse-    [ mkGroup-      [ colored n $ withStrokeWidth (defaultStrokeWidth*0.5) $ withStrokeColor "black" $-        translate x y $ mkCircle circR-      , withFillColor "black" $-        translate x y $ ppNum n ]-    | n <- [0..pSize p-1]-    , let a = realToFrac <$> pAccess p n-          b = realToFrac <$> pAccess p (pNext p n)-          V2 x y = lerp t b a ]-  where-    circR = 0.1-    colored n =-      let c = genColor n (pSize p-1)-      in withFillColorPixel c-    ppNum n = scaleToHeight (circR*1.5) $ center $ latex $ T.pack $ "\\texttt{" ++ show n ++ "}"--polygonShape :: Polygon -> SVG-polygonShape p = mkLinePathClosed-  [ (x,y) | V2 x y <- map (fmap realToFrac) $ V.toList (polygonPoints p) ]
− examples/morphology_rigid.hs
@@ -1,66 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ParallelListComp  #-}-module Main(main) where--import           Codec.Picture-import           Codec.Picture.Types-import           Control.Monad-import           Graphics.SvgTree         (LineJoin (..))-import           Reanimate-import           Reanimate.Morph.Common-import           Reanimate.Morph.Linear-import           Reanimate.Morph.Rigid (rigidMorph)--bgColor :: PixelRGBA8-bgColor = PixelRGBA8 252 252 252 0xFF--main :: IO ()-main = reanimate $-  addStatic (mkBackgroundPixel bgColor) $-  mapA (withStrokeWidth defaultStrokeWidth) $-  mapA (withStrokeColor "black") $-  mapA (withStrokeLineJoin JoinRound) $-  mapA (withFillOpacity 1) $-    sceneAnimation $ do-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (-3) 4 $-        center $ latex "linear"-      _ <- newSpriteSVG $-        withStrokeWidth 0 $ translate (3) 4 $-        center $ latex "as-rigid-as-possible"-      forM_ pairs $ uncurry showPair-  where-    showPair from to =-      waitOn $ do-        fork $ play $ mkAnimation 4 (morph linear from to)-          # mapA (translate (-3) (-0.5) . lowerTransformations . scale 1)-          # signalA (curveS 4)-        fork $ play $ mkAnimation 4 (morph myMorph from to)-          # mapA (translate (3) (-0.5) . lowerTransformations . scale 1)-          # signalA (curveS 4)--    myMorph = linear{morphTrajectory = rigidMorph, morphTolerance = 0.001 }-    pairs = zip stages (tail stages ++ [head  stages])-    stages = map (lowerTransformations . scale 6 . pathify . center) $ colorize-      [ latex "S"-      , latex "C"-      ]-      -- [ latex "X"-      -- , latex "$\\aleph$"-      -- , latex "Y"-      -- , latex "$\\infty$"-      -- , latex "I"-      -- , latex "$\\pi$"-      -- , latex "1"-      -- , latex "S"-      -- , mkRect 0.5 0.5-      -- ]--colorize :: [SVG] -> [SVG]-colorize lst =-  [ withFillColorPixel (promotePixel $ parula (n/fromIntegral (length lst-1))) elt-  | elt <- lst-  | n <- [0..]-  ]
examples/morphology_rotational.hs view
@@ -6,8 +6,9 @@  import           Codec.Picture import           Codec.Picture.Types+import           Control.Lens               ((&)) import           Control.Monad-import           Graphics.SvgTree (LineJoin(..))+import           Graphics.SvgTree           (LineJoin (..)) import           Reanimate import           Reanimate.Morph.Common import           Reanimate.Morph.Linear@@ -35,11 +36,11 @@     showPair from to =       waitOn $ do         fork $ play $ mkAnimation 4 (morph linear from to)-          # mapA (translate (-3) (-0.5))-          # signalA (curveS 4)+          & mapA (translate (-3) (-0.5))+          & signalA (curveS 4)         fork $ play $ mkAnimation 4 (morph myMorph from to)-          # mapA (translate (3) (-0.5))-          # signalA (curveS 4)+          & mapA (translate (3) (-0.5))+          & signalA (curveS 4)     myMorph = linear{morphTrajectory = rotationalTrajectory origin }     origin = (0.5, 0.5)     pairs = zip stages (tail stages ++ [head stages])
examples/morphology_rotational_intro.hs view
@@ -6,6 +6,7 @@ module Main(main) where  import           Codec.Picture+import           Control.Lens               ((&)) import           Data.List                  (transpose) import qualified Data.Vector                as V import           Graphics.SvgTree           (LineJoin (..))@@ -84,9 +85,9 @@     showPair originVar from to =       waitOn $ do         fork $ play $ mkAnimation 2 (morph linear (polygonShape from) (polygonShape to))-          # mapA (translate (-4) (-0.5))-          # mapA (withFillColor "lightgreen")-          # signalA (curveS 4)+          & mapA (translate (-4) (-0.5))+          & mapA (withFillColor "lightgreen")+          & signalA (curveS 4)         s <- fork $ newSprite $ do           origin <- unVar originVar           t <- spriteT
− examples/polyshape_test1.hs
@@ -1,92 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Control.Lens        ()-import           Data.List-import qualified Geom2D.CubicBezier  as G-import           Reanimate-import           Reanimate.PolyShape---polygonTest :: Animation-polygonTest = animate $ \_ ->-    std $ gridLayout $ transpose-      [ test0, test1, test2, test3, test4, test5 ]-  where-    test0 = column ppA poly1-    test1 = column ppA poly2-    test2 = column ppA poly3-    test3 = column ppA poly4-    test4 = column ppB [boxPolyShape]-    test5 = column ppB [starPolyShape]--    column pp poly = map pp-      [ poly-      , map plFromPolygon $ plDecompose' 0.05 poly-      , map plFromPolygon $ plDecompose poly ]---    poly1 =-      svgToPolyShapes $ lowerTransformations $-      center $ scale 3 $-      latex "$\\Phi$"-    poly2 =-      svgToPolyShapes $ lowerTransformations $-      center $ scale 3 $-      latex "$\\vartheta$"-    poly3 =-      svgToPolyShapes $ lowerTransformations $-      center $ scale 3 $-      latex "$\\Xi$"-    poly4 =-      svgToPolyShapes $ lowerTransformations $-      center $ scale 3 $-      latex "$\\Theta$"--    ppA = renderPolyShapes-    ppB = lowerTransformations . scale 0.6 . center . renderPolyShapes-    std =-      withFillOpacity 1 .-      withFillColor "blue" .-      withStrokeWidth 0.01 .-      withStrokeColor "white"---boxPolyShape :: PolyShape-boxPolyShape = PolyShape $ G.ClosedPath-  [(G.Point 0 0, G.JoinLine)-  ,(G.Point 0 3, G.JoinLine)-  ,(G.Point 1 3, G.JoinLine)-  ,(G.Point 1 1, G.JoinLine)-  ,(G.Point 2 1, G.JoinLine)-  ,(G.Point 2 2, G.JoinLine)-  ,(G.Point 0 2, G.JoinLine)-  ,(G.Point 0 3, G.JoinLine)-  ,(G.Point 3 3, G.JoinLine)-  ,(G.Point 3 0, G.JoinLine)-  ]--{--squarePolyShape :: PolyShape-squarePolyShape = PolyShape $ G.ClosedPath-  [(G.Point 0 0, G.JoinLine)-  ,(G.Point 1 0, G.JoinLine)-  ,(G.Point 1 1, G.JoinLine)-  ,(G.Point 0 1, G.JoinLine)]--}--starPolyShape :: PolyShape-starPolyShape = PolyShape $ G.ClosedPath-  [(G.Point 0 0, G.JoinLine)-  ,(G.Point 1 2, G.JoinLine)-  ,(G.Point 2 0, G.JoinLine)-  ,(G.Point 0 1, G.JoinLine)-  ,(G.Point 2 1, G.JoinLine)-  ]--main :: IO ()-main = reanimate $ bg `parA` polygonTest-  where-    bg = animate $ const $ mkBackground "black"
− examples/polyshape_test2.hs
@@ -1,34 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Reanimate-import           Reanimate.PolyShape--polygonTest :: Animation-polygonTest = mkAnimation 10 $ \t ->-    let s = fromToS 0.5 (-0.5) t-        bigBox = head $ svgToPolyShapes $ pathify $-          mkRect 2 2-        smallBox = head $ svgToPolyShapes $ pathify $-          translate 0 (screenHeight*s) $-          rotate (-45) $-          mkRect 1 1--        overlap = mkGroup $ map renderPolyShape [bigBox, smallBox]-        merged = translate (screenWidth/2*0.1) 0 $-          mkGroup $ map renderPolyShape $-          unionPolyShapes [bigBox, smallBox]-    in std $ gridLayout [[ overlap, merged ]]-  where-    std =-      withFillOpacity 1 .-      withFillColor "blue" .-      withStrokeWidth 0.01 .-      withStrokeColor "white"--main :: IO ()-main = reanimate $ bg `parA` polygonTest-  where-    bg = animate $ const $ mkBackground "black"
− examples/polyshape_test3.hs
@@ -1,48 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Reanimate-import           Reanimate.PolyShape-import qualified Geom2D.CubicBezier  as G--polygonTest :: Animation-polygonTest = mkAnimation 10 $ \t ->-    let original =-          renderPolyShapes [iShape]-        originalP =-          renderPolyShapes $ map (plPartial t) [iShape]-        decomposed =-          renderPolyShapes $-          map plFromPolygon $ plDecompose [iShape]-        decomposedP =-          renderPolyShapes $ map (plPartial' t) $ concat $ plGroupTouching $-          map plFromPolygon $ plDecompose $ [iShape]-    in std $ gridLayout-      [[original, originalP]-      ,[decomposed, decomposedP]]-  where-    std =-      withFillOpacity 1 .-      withFillColor "blue" .-      withStrokeWidth 0.02 .-      withStrokeColor "white"--main :: IO ()-main = reanimate $ bg `parA` polygonTest-  where-    bg = animate $ const $ mkBackground "black"--iShape :: PolyShape-iShape = PolyShape $ G.ClosedPath-  [(G.Point 2 2, G.JoinLine)-  ,(G.Point 0 2, G.JoinLine)-  ,(G.Point 0 1, G.JoinLine)-  ,(G.Point 1 1, G.JoinLine)-  ,(G.Point 1 0, G.JoinLine)-  ,(G.Point 3 0, G.JoinLine)-  ,(G.Point 3 1, G.JoinLine)-  ,(G.Point 4 1, G.JoinLine)-  ,(G.Point 4 2, G.JoinLine)-  ]
− examples/polyshape_test4.hs
@@ -1,33 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Reanimate-import           Reanimate.PolyShape--polygonTest :: Animation-polygonTest = mapA std $-    let shapes = svgToPolyShapes $ center $ scale 6 $ latex "$\\infty$"-        groups =-          plGroupTouching $-          map plFromPolygon $ plDecompose $ shapes-        -- totalLen = sum $ map (maximum . map (plArea.snd)) groups-        totalLen = fromIntegral $ length groups-        anis = [ setDuration dur $ animate $ \t -> renderPolyShapes $ map (plPartial' t) group-               | group <- groups-               , let groupLen = 1 -- maximum (map (plArea.snd) group)-                     dur = groupLen/totalLen * totalT ]-    in foldr andThen (pause 0) anis-  where-    totalT = 5-    std =-      withFillOpacity 1 .-      withFillColor "blue" .-      withStrokeWidth 0.00 .-      withStrokeColor "white"--main :: IO ()-main = reanimate $ bg `parA` polygonTest-  where-    bg = animate $ const $ mkBackground "black"
− examples/shatter.hs
@@ -1,135 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Chiphunk.Low-import           Control.Monad-import           Graphics.SvgTree    (Tree)-import           Linear.V2-import           Reanimate-import           Reanimate.Chiphunk-import           Reanimate.PolyShape-import           System.IO.Unsafe--test :: Animation-test = unsafePerformIO $ do-  bodyStore <- newBodyStore-  let gravity = Vect 0 (-1)--  -- Create an empty space.-  space <- spaceNew-  spaceCollisionSlop space $= (screenWidth/2560)-  spaceGravity space $= gravity--  -- Add a static line segment shape for the ground.-  -- We'll make it slightly tilted so the ball will roll off.-  -- We attach it to a static body to tell Chipmunk it shouldn't be movable.-  static <- get $ spaceStaticBody space-  ground <- segmentShapeNew static-    (Vect (-screenWidth/2) 0)-    (Vect (screenWidth/2) (-screenHeight/2)) 0-  -- ground <- polyShapeNewRaw static-  --   [ Vect (-screenWidth/2) (screenHeight/2)-  --   , Vect (screenWidth/2) (-screenHeight/2)-  --   , Vect (-screenWidth/2) (-screenHeight/2)-  --   , Vect (-screenWidth/2) (screenHeight/2) ] 0-  shapeFriction ground $= 1-  spaceAddShape space ground----  -- Now let's make a ball that falls onto the line and rolls off.-  -- First we need to make a cpBody to hold the physical properties of the object.-  -- These include the mass, position, velocity, angle, etc. of the object.-  -- Then we attach collision shapes to the Body to give it a size and shape.--  let toVect (V2 x y) = Vect x y--  let svg = center $ scale 2 $ latex "\\LaTeX"-      poly = svgToPolyShapes svg-      vectGroup = plDecompose poly-            --mkCircle (Num radius)-      -- vects = svgToVects svg-      -- vects' = fst $ convexHull vects 0-      -- svg' =-      --   withFillOpacity 0 $ withStrokeColor "white" $-      --   withStrokeWidth 0.01 $-      --   renderPolyShapes (map plFromPolygon vectGroup)-  ---  -- ballBody <- polyShapesToBody space poly-  -- bodyPosition ballBody $= Vect (-screenWidth/4) (screenHeight/2)-  ---  -- addToBodyStore bodyStore ballBody $-  --   withFillColor "white" $-  --   mkGroup-  --     [ svg' ]--  -- let splitPolys = vectGroup-  forM_ vectGroup $ \polygon -> do-    bd <- polygonsToBody space [map toVect polygon]-    bodyPosition bd $= Vect 0 (screenHeight/3)-    addToBodyStore bodyStore bd $-      renderPolyShape $ plFromPolygon polygon-      -- withFillColor "white" $-      -- mkGroup-      --   [ --withStrokeWidth (Num 0.005) $-      --     -- withStrokeWidth (Num 0.00) $-      --     -- withStrokeColor "white" $-      --     -- withFillOpacity 1 $-      --     renderPolyShape $ plFromPolygon polygon-      --   ]--  ani <- simulate space bodyStore 60 60 10-  spaceFreeRecursive space-  return ani---- data LineCommand---   = LineMove RPoint---   -- | LineDraw RPoint---   | LineBezier [RPoint]---   | LineEnd---- vectsToSVG :: [Vect] -> Tree--- vectsToSVG (Vect x y:rest) =---   mkPath $---     MoveTo OriginAbsolute [V2 x y] :---     [ LineTo OriginAbsolute [V2 a b] | Vect a b <- rest ] ++---     [ EndPath ]---   where---     mkPath cmds = PathTree $ defaultSvg & pathDefinition .~ cmds---- polygonsToSVG :: [[Vect]] -> Tree--- polygonsToSVG = merge . mkGroup . map vectsToSVG---   where---     merge svg = PathTree $ defaultSvg & pathDefinition .~ extractPath svg---- svgToVects :: Tree -> [Vect]--- svgToVects svg = map worker (lineToPoints 200 cmds)---   where---     worker (V2 x y) = Vect x y---     cmds = toLineCommands $ wibble $ extractPath svg---     wibble xs = takeWhile (/=EndPath) xs ++ [EndPath]--chunkPolyshapes :: Tree -> Tree-chunkPolyshapes t =-  withStrokeColor "white" $-  withStrokeWidth 0.01 $-  withFillColor "white" $ t---- plArea :: PolyShape -> Double--- plArea pl = areaForPoly (map toVect $ plPolygonify polyShapeTolerance pl) 0---   where---     toVect (Point x y) = Vect x y--reorient :: Tree -> Tree-reorient = id -- scale 4 . translate 0 (-0.9)--main :: IO ()-main = reanimate $ bg `parA` mapA reorient (line `parA` mapA chunkPolyshapes test)-  where-    bg = animate $ const $ mkBackground "black"-    line = animate $ const $ withStrokeColor "white" $-      withStrokeWidth 0.01 $-      mkLine (-screenWidth/2, 0)-             (screenWidth/2, -screenHeight/2)
− examples/signals.hs
@@ -1,84 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-module Main (main) where--import           Data.Text           (Text, pack)-import           Graphics.SvgTree    hiding (Text)-import           Numeric-import           Reanimate.Animation-import           Reanimate.Constants-import           Reanimate--main :: IO ()-main = reanimate $ pauseAtEnd 5 $-    curvesExample (\_ -> ([], "[]"))-      `seqA`-    curvesExample (\s ->-      ( [(1, constantS s)]-      , "[(1, constantS " <> ppD s <> ")]"))-      `seqA`-    curvesExample (\s ->-      ( [(s, constantS 0), (1, id)]-      , "[(" <> ppD s <> ", constantS 0), (1, id)]"))-      `seqA`-    curvesExample (\s ->-      ( [(s, constantS 1), (1, reverseS)]-      , "[(" <> ppD s <> ", constantS 1), (1, reverseS)]"))-      `seqA`-    curvesExample (\s ->-      ( [(1, curveS (2+s*3))]-      , "[(1, curveS "<> ppD (2+s*3) <>")]"))-      `seqA`-    curvesExample (\s ->-      ( [(1, fromToS s 1 . curveS 5)]-      , "[(1, fromToS "<>  ppD s <>" 1 \\$ curveS 5)]"))-      `seqA`-    curvesExample (\s ->-      ( [(1, bellS (2+s*3))]-      , "[(1, bellS "<> ppD (2+s*3)<>")]"))-  where-    ppD s = pack (showFFloat (Just 2) s "")--convertX, convertY :: Double -> Double-convertX x = x*(screenWidth/320)-convertY y = y*(screenHeight/180)--curvesExample :: (Double -> ([(Double, Double -> Double)], Text)) -> Animation-curvesExample gen = mkAnimation 2 $ \t ->-    mkGroup-    [ mkBackground "black"-    , withFillColor "white" $-      translate 0 (screenHeight*0.35) $-      center $ latex "Signals"-    , let (curveFns, name) = gen t in-      center $-      mkGroup-        [ withStrokeColor "white" $ withStrokeWidth 0.01 $-          mkGroup-          [ mkLine (0, 0)-                   (convertX 200, 0)-          , mkLine (0, 0)-                   (0, convertY 50) ]-        , withStrokeColor "white" $ withStrokeWidth 0.01 $-          mkGroup-          [ mkLine (0, convertX $ y)-                   (convertX 200, convertX y)-          | y <- [10,20,30,40,50] ]-        , withFillColor "white" $-          mkGroup-            [ translate (convertX $ -5) (convertX $ -5)  $ scale 0.5 $ center $ latex "0"-            , translate (convertX $ -5) (convertX $ 50)  $ scale 0.5 $ center $ latex "1"-            , translate (convertX $ 205) (convertX $ -5) $ scale 0.5 $ center $ latex "1"-            , translate (convertX $ 100) (convertX $ -30)$ scale 0.6 $ center $ latex name ]-        , withFillOpacity 0 $ withStrokeColor "green" $ -- withStrokeWidth 0.5 $-          lowerTransformations $ scaleXY (convertX $ 200) (convertX $ 50) $ mkSignalLine (fromListS curveFns)-        ]-    ]--mkSignalLine :: Signal -> Tree-mkSignalLine fn = mkLinePath-    [ (x/steps, fn (x/steps))-    | x <- [0 .. steps] ]-  where-    steps = 1000
− examples/simulate_equation.hs
@@ -1,201 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-module Main (main) where--import           Chiphunk.Low-import           Control.Monad-import           Graphics.SvgTree    (Tree)-import           Linear.V2-import           Reanimate.Chiphunk-import           Reanimate-import           Reanimate.PolyShape-import           System.IO.Unsafe--test :: Animation-test = unsafePerformIO $ do-  bodyStore <- newBodyStore-  let gravity = Vect 0 (-1)--  -- Create an empty space.-  space <- spaceNew-  -- spaceCollisionSlop space $= (screenWidth/2560)-  spaceGravity space $= gravity--  -- Add a static line segment shape for the ground.-  -- We'll make it slightly tilted so the ball will roll off.-  -- We attach it to a static body to tell Chipmunk it shouldn't be movable.-  static <- get $ spaceStaticBody space-  ground <- segmentShapeNew static-    (Vect (-screenWidth/2) (screenHeight/2))-    (Vect (screenWidth/2) (-screenHeight/2)) 0-  shapeFriction ground $= 1-  spaceAddShape space ground--  -- Now let's make a ball that falls onto the line and rolls off.-  -- First we need to make a cpBody to hold the physical properties of the object.-  -- These include the mass, position, velocity, angle, etc. of the object.-  -- Then we attach collision shapes to the Body to give it a size and shape.--  let toVect (V2 x y) = Vect x y--  let svg = center $ scale 2 $ latex "$\\Phi$"-      poly = svgToPolyShapes svg-      vectGroup = plDecompose' 1 poly-            --mkCircle (Num radius)-      -- vects = svgToVects svg-      -- vects' = fst $ convexHull vects 0-      -- svg' =-      --   withFillOpacity 0 $ withStrokeColor "white" $-      --   withStrokeWidth 0.01 $-      --   renderPolyShapes (map plFromPolygon vectGroup)-  ---  -- ballBody <- polyShapesToBody space poly-  -- bodyPosition ballBody $= Vect (-screenWidth/4) (screenHeight/2)-  ---  -- addToBodyStore bodyStore ballBody $-  --   withFillColor "white" $-  --   mkGroup-  --     [ svg' ]--  -- let splitPolys = vectGroup-  forM_ vectGroup $ \polygon -> do-    bd <- polygonsToBody space [map toVect polygon]-    bodyPosition bd $= Vect 0 (screenHeight/3)-    addToBodyStore bodyStore bd $-      renderPolyShape $ plFromPolygon polygon-      -- withFillColor "white" $-      -- mkGroup-      --   [ --withStrokeWidth (Num 0.005) $-      --     -- withStrokeWidth (Num 0.00) $-      --     -- withStrokeColor "white" $-      --     -- withFillOpacity 1 $-      --     renderPolyShape $ plFromPolygon polygon-      --   ]--  ani <- simulate space bodyStore 60 10 10-  spaceFreeRecursive space-  return ani---- data LineCommand---   = LineMove RPoint---   -- | LineDraw RPoint---   | LineBezier [RPoint]---   | LineEnd---- vectsToSVG :: [Vect] -> Tree--- vectsToSVG (Vect x y:rest) =---   mkPath $---     MoveTo OriginAbsolute [V2 x y] :---     [ LineTo OriginAbsolute [V2 a b] | Vect a b <- rest ] ++---     [ EndPath ]---   where---     mkPath cmds = PathTree $ defaultSvg & pathDefinition .~ cmds---- polygonsToSVG :: [[Vect]] -> Tree--- polygonsToSVG = merge . mkGroup . map vectsToSVG---   where---     merge svg = PathTree $ defaultSvg & pathDefinition .~ extractPath svg---- svgToVects :: Tree -> [Vect]--- svgToVects svg = map worker (lineToPoints 200 cmds)---   where---     worker (V2 x y) = Vect x y---     cmds = toLineCommands $ wibble $ extractPath svg---     wibble xs = takeWhile (/=EndPath) xs ++ [EndPath]--chunkPolyshapes :: Tree -> Tree-chunkPolyshapes t =-  mkGroup-  [-  -- withFillColor "white" $-  -- withFillOpacity 1 $-  -- withStrokeWidth (Num 0.01) $-  -- withStrokeColor "blue" $-  -- mkGroup $ map renderPolyShape $-  -- map mergePolyShapeHoles $-  -- plGroupShapes $-  -- -- unionPolyShapes $-  -- traceUnion $-  -- svgToPolyShapes t-    withStrokeWidth 0.001 $-    withStrokeColor "red" $-    withFillOpacity 0 $-    mkGroup $ map renderPolyShape $-    svgToPolyShapes t-    -- withStrokeWidth 0.001 $-    -- withStrokeColor "red" $-    -- mkGroup $ map renderPolyShape [pl12,pl13]-  ]---- plArea :: PolyShape -> Double--- plArea pl = areaForPoly (map toVect $ plPolygonify polyShapeTolerance pl) 0---   where---     toVect (Point x y) = Vect x y--reorient :: Tree -> Tree-reorient = scale 6 . translate 0 (-0.9)---- traceUnion t =---   let out = unionPolyShapes t---   in trace (unlines $---             ("Trace: " ++ show (sum $ map plArea t)) :---             ("Check: " ++ show (sum $ map plArea out)) :---             map show t ++ ["Out:"] ++ map show out) out---- pl1 = PolyShape {unPolyShape = ClosedPath [(Point 9.076152025049407e-2 0.8793158714390639,JoinLine),(Point 0.48865783997158957 0.5875446050720555,JoinLine),(Point 0.6291445682322502 0.560956966929786,JoinLine),(Point 0.684414274089106 0.6254399758525935,JoinLine),(Point 8.549729225029659e-2 0.9226054512958702,JoinLine)]}--- pl2 = PolyShape {unPolyShape = ClosedPath [(Point 0.3174796674934045 (-0.17830385091116296),JoinLine),(Point 0.3930521201476444 (-0.22384314349780887),JoinLine),(Point 0.42400439387727545 (-0.17026635397131717),JoinLine),(Point 0.367180526197663 (-0.13743818486413273),JoinLine)]}--- pl3 = PolyShape {unPolyShape = ClosedPath [(Point 3.818529643334073e-2 4.994390978944008e-2,JoinLine),(Point 3.5509598929490127e-3 3.0061273354143103e-2,JoinLine),(Point 2.6643683455205014e-2 (-1.3314171733262437e-2),JoinLine)]}--- pl4 = PolyShape {unPolyShape = ClosedPath [(Point 8.041123459068245e-2 (-4.592522065745819e-2),JoinLine),(Point 0.29675416515553454 (-0.17235110729572378),JoinLine),(Point 0.34590008699238584 (-0.13081971694800998),JoinLine),(Point 0.22737727571852562 4.513304505856075e-2,JoinLine),(Point 0.10498823211784264 (-3.373201779345303e-3),JoinLine)]}--- pl5 = PolyShape {unPolyShape = ClosedPath [(Point 4.1547772506933034e-2 0.10908491253751296,JoinLine),(Point 5.7556283592893165e-2 (-2.2936657573235042e-2),JoinLine),(Point 0.18645395693905423 2.887370173550552e-2,JoinLine),(Point 0.24063435970720265 0.12675372820097613,JoinLine),(Point 0.19807899284556882 0.17285980812856805,JoinLine),(Point 8.45668830191807e-2 0.18680141998908184,JoinLine)]}--- pl6 = PolyShape {unPolyShape = ClosedPath [(Point (-0.2574090456009813) 0.22352645168090446,JoinLine),(Point (-0.27139525022076605) 0.16073053060077958,JoinLine),(Point (-9.444562231853837e-2) 4.994785063168361e-2,JoinLine),(Point (-4.012849544398306e-2) 6.733655689772311e-2,JoinLine),(Point (-4.6550067413171026e-2) 0.20017002713448606,JoinLine)]}--- pl7 = PolyShape {unPolyShape = ClosedPath [(Point (-0.3714373753139388) 0.20874389306710772,JoinLine),(Point (-0.29452803703157854) 0.1655072120141602,JoinLine),(Point (-0.2835217605861194) 0.22889335799870397,JoinLine),(Point (-0.34012040364470725) 0.2621083279509192,JoinLine)]}--- pl8 = PolyShape {unPolyShape = ClosedPath [(Point 0.1712163207191154 0.2149766965620984,JoinLine),(Point 0.19616659305280632 0.1905738263277067,JoinLine),(Point 0.6315796735654418 0.5328570813993471,JoinLine),(Point 0.49895896036053317 0.5862908698518825,JoinLine)]}--- pl9 = PolyShape {unPolyShape = ClosedPath [(Point 0.10739670009207455 0.19390054402859663,JoinLine),(Point 0.16776933204268935 0.17681618959615353,JoinLine),(Point 0.19166035517967667 0.1970425575035583,JoinLine),(Point 0.16584071117957827 0.2205236594561943,JoinLine)]}--- pl10 = PolyShape {unPolyShape = ClosedPath [(Point (-0.3669271639958152) 1.1382616686769857,JoinLine),(Point (-0.30169633405804164) 1.1454435521784956,JoinLine),(Point (-0.2858326377769962) 1.2078015601462844,JoinLine),(Point (-0.3736986541543818) 1.199765022618315,JoinLine)]}--- pl11 = PolyShape {unPolyShape = ClosedPath [(Point (-3.0061173114158422e-2) 1.1779482589541495,JoinLine),(Point 5.4907529076574785e-3 1.2315286122957358,JoinLine),(Point (-3.4177530164954735e-2) 1.2269151735259667,JoinLine)]}--- pl12 = PolyShape {unPolyShape = ClosedPath---   [(Point (-0.301730083226429*10) 1.1457103198223786,JoinLine)---   ,(Point (-0.10299641326632633*10) 1.0714677466302471,JoinLine)---   ,(Point (-3.162369257155477e-2*10) 1.182092647608354,JoinLine)---   ,(Point (-3.636877035247056e-2*10) 1.2310026399913667,JoinLine)---   ,(Point (-0.28589249688644447*10) 1.208074964264014,JoinLine)]}--- pl13 = PolyShape {unPolyShape = ClosedPath---   [(Point (-9.39646610882567e-2*10) 0.96018603098516,JoinLine)---   ,(Point (-6.465077411411442e-2*10) 0.9514465910530718,JoinLine)---   ,(Point (0.14134955357291654*10) 1.2461928296570437,JoinLine)---   ,(Point (8.29425965486209e-3*10) 1.2330682613363688,JoinLine)---   ,(Point (-0.10287959610529117*10) 1.0717052650151784,JoinLine)]}--- pl14 = PolyShape {unPolyShape = ClosedPath [(Point (-0.6410657779553459) 0.5634659418454123,JoinLine),(Point (-0.641345175626591) 0.555247219163176,JoinLine),(Point (-0.4420444191709594) 0.5513358399243028,JoinLine),(Point (-0.10594072095664082) 0.9023816092645105,JoinLine),(Point (-7.577913627887163e-2) 0.9355404742795719,JoinLine),(Point (-0.10373629906690308) 0.9479533236181756,JoinLine)]}--- pl15 = PolyShape {unPolyShape = ClosedPath [(Point (-3.523485505752828e-2) 0.1998408875682416,JoinLine),(Point 1.2546982880291841e-2 0.15208851089779252,JoinLine),(Point 0.13018455413761065 0.208301106923033,JoinLine),(Point 8.433578523127425e-2 0.9418697065712005,JoinLine),(Point (-7.884702019521485e-2) 0.932428903501462,JoinLine),(Point (-8.084970167352624e-2) 0.9296667902794287,JoinLine)]}--- pl16 = PolyShape {unPolyShape = ClosedPath [(Point (-7.878197627394322e-2) 0.9319709148544943,JoinLine),(Point 8.438182768423004e-2 0.941734614325483,JoinLine),(Point 8.518191684165782e-2 0.9853357574063363,JoinLine),(Point 8.15663333974107e-2 0.9853939259545145,JoinLine),(Point (-4.936159754845773e-2) 0.9727171460725276,JoinLine)]}--- pl17 = PolyShape {unPolyShape = ClosedPath [(Point (-4.99911351869741e-2) 0.9726181699897635,JoinLine),(Point 8.094809532371453e-2 0.9851776987711782,JoinLine),(Point 7.384845354621125e-2 1.0968271981504014,JoinLine),(Point 4.400324523877765e-2 1.1025518925313644,JoinLine)]}--- pl18 = PolyShape {unPolyShape = ClosedPath [(Point 4.8482888984171635e-2 1.1105768787999895,JoinLine),(Point 7.512899062784217e-2 1.0959654499901599,JoinLine),(Point 0.2822087302816073 1.1420633097553252,JoinLine),(Point 0.27513287205836434 1.2060076125068004,JoinLine),(Point 0.20204313118994668 1.2231219927110881,JoinLine)]}--- pl19 = PolyShape {unPolyShape = ClosedPath [(Point 0.2748784478150325 1.2078902086770202,JoinLine),(Point 0.2820822061918643 1.1439601884266248,JoinLine),(Point 0.3457225261391361 1.127943000720636,JoinLine),(Point 0.36082444597621127 1.187946730956635,JoinLine)]}--- pl20 = PolyShape {unPolyShape = ClosedPath [(Point (-0.6864531908080556) 0.5032767356555317,JoinLine),(Point (-9.929294916146593e-2) 0.20829491627795255,JoinLine),(Point (-8.483230342755331e-2) 0.23523598173633797,JoinLine),(Point (-0.49217693932265094) 0.547917843618004,JoinLine)]}--- pl21 = PolyShape {unPolyShape = ClosedPath [(Point (-9.274301086931341e-2) 0.2153671046631685,JoinLine),(Point (-4.68781647762479e-2) 0.2005165600411279,JoinLine),(Point (-4.790067311086227e-2) 0.23865704773924823,JoinLine),(Point (-7.795810209993242e-2) 0.2330461435659677,JoinLine)]}---- unionTest = parsed---   where---     svg = center $ scale 2 $ latex "I"---     poly = svgToPolyShapes svg---     vectGroup = plDecompose' 1 poly---     rendered = mkGroup $ take 100 $ map (renderPolyShape . plFromPolygon) vectGroup---     parsed = svgToPolyShapes rendered------     myDecompose tol =---       concatMap decomposePolygon .---       map (plPolygonify tol) .---       map mergePolyShapeHoles .---       plGroupShapes .---       unionPolyShapes--main :: IO ()-main = reanimate $ bg `parA` mapA reorient (line `parA` mapA chunkPolyshapes test)-  where-    bg = animate $ const $ mkBackground "black"-    line = animate $ const $ withStrokeColor "white" $-      withStrokeWidth 0.01 $-      mkLine (-screenWidth/2, screenHeight/2)-             (screenWidth/2, -screenHeight/2)
− examples/simulate_gravity.golden
@@ -1,10 +0,0 @@-0<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(0, 4.5)"><g transform="rotate(0)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-1<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(0, 3.5)"><g transform="rotate(0)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-2<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(0, 0.623148)"><g transform="rotate(0)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-3<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(0.544434, -1.371641)"><g transform="rotate(-32.404054)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-4<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(1.709402, -1.699288)"><g transform="rotate(-101.741451)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-5<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(3.264515, -2.136663)"><g transform="rotate(-194.299838)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-6<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(5.09458, -2.651369)"><g transform="rotate(-303.223039)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-7<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(7.340372, -3.282998)"><g transform="rotate(-436.88976)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-8<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(9.775043, -4.315897)"><g transform="rotate(-581.798313)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>-9<svg viewBox="-8 -4.5 16 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="none"><g stroke-width="0.05" transform="scale(1, -1)"><g><g><g stroke-width="0" fill="#000000" transform="translate(-8, -4.5)" fill-opacity="1"><rect width="16" height="9" /></g><line stroke-width="0.01" stroke="#FFFFFF" x1="-8" x2="8" y2="-4.5" /></g><g><g transform="translate(12.329251, -7.234268)"><g transform="rotate(-733.821586)"><g fill="#FFFFFF"><circle r="1" /><line stroke="#000000" y2="1" /></g></g></g></g></g></g></svg>
− examples/simulate_gravity.hs
@@ -1,79 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-module Main (main) where--import           Chiphunk.Low-import           Reanimate.Chiphunk-import           Reanimate-import           System.IO.Unsafe---test :: Animation-test = unsafePerformIO $ do-  bodyStore <- newBodyStore-  let gravity = Vect 0 (-10)--  -- Create an empty space.-  space <- spaceNew-  spaceGravity space $= gravity--  -- Add a static line segment shape for the ground.-  -- We'll make it slightly tilted so the ball will roll off.-  -- We attach it to a static body to tell Chipmunk it shouldn't be movable.-  static <- get $ spaceStaticBody space-  ground <- segmentShapeNew static-    (Vect (-screenWidth/2) 0)-    (Vect (screenWidth/2) (-screenHeight/2)) 0-  shapeFriction ground $= 1-  spaceAddShape space ground--  -- Now let's make a ball that falls onto the line and rolls off.-  -- First we need to make a cpBody to hold the physical properties of the object.-  -- These include the mass, position, velocity, angle, etc. of the object.-  -- Then we attach collision shapes to the Body to give it a size and shape.--  let radius = 1-  let mass = 1--  -- The moment of inertia is like mass for rotation-  -- Use the momentFor* functions to help you approximate it.-  let moment = momentForCircle mass 0 radius (Vect 0 0)--  -- The spaceAdd* functions return the thing that you are adding.-  ballBody <- bodyNew mass moment-  spaceAddBody space ballBody-  bodyPosition ballBody $= Vect 0 (screenHeight/2)--  -- Now we create the collision shape for the ball.-  -- You can create multiple collision shapes that point to the same body.-  -- They will all be attached to the body and move around to follow it.-  ballShape <- circleShapeNew ballBody radius (Vect 0 0)-  spaceAddShape space ballShape-  shapeFriction ballShape $= 0.7--  addToBodyStore bodyStore ballBody $-    withFillColor "white" $-    mkGroup-      [ mkCircle radius-      , withStrokeColor "black" $-        mkLine (0, 0) (0, radius) ]--  ani <- simulate space bodyStore 60 3 4--  shapeFree ballShape-  bodyFree ballBody-  shapeFree ground-  spaceFree space-  return ani---main :: IO ()-main = reanimate $ bg `parA` line `parA` test-  where-    bg = animate $ const $ mkBackground "black"-    line = animate $ const $ withStrokeColor "white" $-      withStrokeWidth 0.01 $-      mkLine (-screenWidth/2, 0)-             (screenWidth/2, -screenHeight/2)
examples/sorting.hs view
@@ -2,6 +2,7 @@ -- stack runghc --package reanimate {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes        #-}+{-# LANGUAGE PackageImports    #-} module Main (main) where  import           Codec.Picture@@ -13,7 +14,7 @@ import qualified Data.Vector.Unboxed         as V import           Reanimate import           System.Random-import           System.Random.Shuffle+import "random-shuffle" System.Random.Shuffle  main :: IO () main = reanimate $
examples/tut_glue_blender.hs view
@@ -3,20 +3,21 @@ {-# LANGUAGE ApplicativeDo     #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE PackageImports    #-} module Main (main) where  import           Reanimate import           Reanimate.Builtin.Documentation  import           Codec.Picture.Types-import           Control.Lens                    ((^.))+import           Control.Lens                    ((&), (^.)) import           Control.Monad import           Data.Monoid import qualified Data.Text                       as T import           Graphics.SvgTree                hiding (text) import           NeatInterpolation import           System.Random-import           System.Random.Shuffle+import "random-shuffle" System.Random.Shuffle  -- spritePercent = (/) <$> spriteT <*> spriteDur @@ -216,7 +217,7 @@     mapM_ destroySprite sprites     -- Undraw equations     play $ drawAnimation' (Just 0xdeadbeef) 1 0.1 strokedSvg-      # reverseA+      & reverseA   where     glyphs = svgGlyphs svg     strokedSvg =@@ -258,7 +259,7 @@     fork $ do       wait (n*step)       play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)-        # applyE (overEnding fillDur $ fadeLineOutE sWidth))+        & applyE (overEnding fillDur $ fadeLineOutE sWidth))     fork $ do       wait (n*step+(1-fillDur))       newSprite $ do
examples/tut_glue_fourier.hs view
@@ -3,26 +3,26 @@ {-# LANGUAGE OverloadedStrings #-} module Main (main) where +import           Codec.Picture+import           Control.Lens import           Data.Complex import           Graphics.SvgTree import           Linear.V2 import           Reanimate-import           Reanimate.Ease-import           Codec.Picture  -- layer 3 main :: IO () main = reanimate $ setDuration 30 $ sceneAnimation $ do     _ <- newSpriteSVG $ mkBackgroundPixel (PixelRGBA8 252 252 252 0xFF)     play $ fourierA (fromToS 0 5)      -- Rotate 15 times-      # setDuration 50-      # signalA (reverseS . powerS 2 . reverseS) -- Start fast, end slow-      # pauseAtEnd 2+      & setDuration 50+      & signalA (reverseS . powerS 2 . reverseS) -- Start fast, end slow+      & pauseAtEnd 2     play $ fourierA (constantS 0)       -- Don't rotate at all-      # setDuration 10-      # reverseA-      # signalA (powerS 2)                       -- Start slow, end fast-      # pauseAtEnd 2+      & setDuration 10+      & reverseA+      & signalA (powerS 2)                       -- Start slow, end fast+      & pauseAtEnd 2  -- layer 2 fourierA :: (Double -> Double) -> Animation
examples/tut_glue_keyframe.hs view
@@ -4,11 +4,11 @@ {-# LANGUAGE RecursiveDo       #-} module Main (main) where -import           Reanimate-+import           Codec.Picture+import           Control.Lens import           Control.Monad    (forM_) import           Graphics.SvgTree (Tree)-import           Codec.Picture+import           Reanimate  main :: IO () main = reanimate $ bg `parA` mainScene@@ -18,30 +18,30 @@ mainScene :: Animation mainScene = sceneAnimation $ mdo     play $ drawCircle-      # setDuration drawCircleT-      # applyE (constE flipXAxis)-      # signalA (curveS 2)+      & setDuration drawCircleT+      & applyE (constE flipXAxis)+      & signalA (curveS 2)     fork $ play $ drawCircle-      # freezeAtPercentage 1-      # setDuration rotDur+      & freezeAtPercentage 1+      & setDuration rotDur     rotDur <- withSceneDuration $ waitOn $       forM_ svgs $ \svg -> do         fork $ play $ drawTick-          # setDuration rotateT-          # repeatA rotateN-          # applyE (overBeginning 0.5 drawInE)-          # applyE (overEnding 0.5 drawOutE)+          & setDuration rotateT+          & repeatA rotateN+          & applyE (overBeginning 0.5 drawInE)+          & applyE (overEnding 0.5 drawOutE)         fork $ play $ drawSVG svg-          # setDuration rotateT-          # repeatA rotateN-          # applyE (overBeginning rotateT drawInE)-          # applyE (delayE rotateT $ overBeginning 1 fillInE)-          # applyE (overEnding 0.5 fadeOutE)+          & setDuration rotateT+          & repeatA rotateN+          & applyE (overBeginning rotateT drawInE)+          & applyE (delayE rotateT $ overBeginning 1 fillInE)+          & applyE (overEnding 0.5 fadeOutE)         wait (rotateT / fromIntegral (1+length svgs))     play $ drawCircle-      # setDuration drawCircleT-      # reverseA-      # signalA (curveS 2)+      & setDuration drawCircleT+      & reverseA+      & signalA (curveS 2)     return ()   where     drawCircleT = 2.5
examples/tut_glue_latex.hs view
@@ -1,19 +1,20 @@ #!/usr/bin/env stack -- stack runghc --package reanimate+{-# LANGUAGE ApplicativeDo     #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE PackageImports    #-} module Main (main) where  import           Reanimate  import           Codec.Picture import           Codec.Picture.Types-import           Control.Lens          ((^.))+import           Control.Lens          ((&), (^.)) import           Control.Monad import           Data.Monoid import           Graphics.SvgTree import           System.Random-import           System.Random.Shuffle+import "random-shuffle" System.Random.Shuffle  main :: IO () main = reanimate $ parA bg latexExample@@ -38,7 +39,7 @@     mapM_ destroySprite sprites     -- Undraw equations     play $ drawAnimation' (Just 0xdeadbeef) 1 0.1 strokedSvg-      # reverseA+      & reverseA   where     glyphs = svgGlyphs svg     strokedSvg =@@ -80,7 +81,7 @@     fork $ do       wait (n*step)       play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)-        # applyE (overEnding fillDur $ fadeLineOutE sWidth))+        & applyE (overEnding fillDur $ fadeLineOutE sWidth))     fork $ do       wait (n*step+(1-fillDur))       newSprite $ do
− examples/tut_glue_physics.hs
@@ -1,59 +0,0 @@-#!/usr/bin/env stack--- stack runghc --package reanimate-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-module Main (main) where--import           Chiphunk.Low-import           Codec.Picture       (PixelRGBA8 (..))-import           Control.Monad       (forM_)-import           Linear.V2           (V2 (..))-import           Reanimate-import           Reanimate.Chiphunk-import           Reanimate.PolyShape-import           Reanimate.Ease-import           System.IO.Unsafe    (unsafePerformIO)--shatter :: Animation-shatter = unsafePerformIO $ do-    bodyStore <- newBodyStore-    let gravity = Vect 0 (-1) -- Gravity points down (negative 1 y/s^2)--    space <- spaceNew-    spaceGravity space $= gravity--    static <- get $ spaceStaticBody space-    ground <- segmentShapeNew static-      (Vect (-screenWidth/2) (-screenHeight/2))-      (Vect (screenWidth/2) (-screenHeight/2)) 0-    shapeFriction ground $= 1-    spaceAddShape space ground--    let toVect (V2 x y) = Vect x y-        vectGroup = plDecompose $ svgToPolyShapes $ center $ scale 4 $-          latex "$F=ma$"--    forM_ vectGroup $ \polygon -> do-      bd <- polygonsToBody space [map toVect polygon]-      bodyPosition bd $= Vect 0 (screenHeight/3)-      addToBodyStore bodyStore bd $-        renderPolyShape $ plFromPolygon polygon--    ani <- simulate space bodyStore fps stepsPerFrame shatterT-    spaceFreeRecursive space-    return $ mapA pp ani-  where-    shatterT = 10-    fps = 60-    stepsPerFrame = 10-    pp = withStrokeWidth 0.01 . withStrokeColor "black" . withFillColor "black"--main :: IO ()-main = reanimate $ parA bg $ sceneAnimation $ do-    play $ shatter-    play $ shatter-      # reverseA-      # setDuration 5-      # signalA (powerS 2)-  where-    bg = animate $ const $ mkBackgroundPixel (PixelRGBA8 252 252 252 0xFF)
examples/tut_glue_potrace.hs view
@@ -8,6 +8,7 @@ import           Reanimate.Povray  (povraySlow')  import           Codec.Picture+import           Control.Lens import           Control.Monad import           Data.Text         (Text) import qualified Data.Text         as T@@ -25,7 +26,7 @@       tweenVar xRot (wobbleDur/2) $ \v -> fromToS v (v-90) . curveS 2     destroySprite wf     play $ mkAnimation drawDuration (\t -> partialSvg t (wireframe (-45) 220))-      # reverseA+      & reverseA   where     drawDuration = 10     wobbles = 3
examples/tut_glue_povray.hs view
@@ -1,8 +1,9 @@ #!/usr/bin/env stack -- stack runghc --package reanimate+{-# LANGUAGE ApplicativeDo     #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}-{-# LANGUAGE ApplicativeDo     #-}+{-# LANGUAGE PackageImports    #-} module Main (main) where  import           Reanimate@@ -10,15 +11,15 @@  import           Codec.Picture import           Codec.Picture.Types-import           Control.Lens          ((^.))+import           Control.Lens          ((&), (^.)) import           Control.Monad-import qualified Data.Text             as T import           Data.Monoid-import           NeatInterpolation import           Data.Text             (Text)-import           Graphics.SvgTree      hiding (Text,text)+import qualified Data.Text             as T+import           Graphics.SvgTree      hiding (Text, text)+import           NeatInterpolation import           System.Random-import           System.Random.Shuffle+import "random-shuffle" System.Random.Shuffle   main :: IO ()@@ -109,7 +110,7 @@     mapM_ destroySprite sprites     -- Undraw equations     play $ drawAnimation' (Just 0xdeadbeef) 1 0.1 strokedSvg-      # reverseA+      & reverseA   where     glyphs = svgGlyphs svg     strokedSvg =@@ -151,7 +152,7 @@     fork $ do       wait (n*step)       play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)-        # applyE (overEnding fillDur $ fadeLineOutE sWidth))+        & applyE (overEnding fillDur $ fadeLineOutE sWidth))     fork $ do       wait (n*step+(1-fillDur))       newSprite $ do
examples/tut_glue_povray_ortho.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE ApplicativeDo     #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE PackageImports    #-} module Main (main) where  import           Reanimate@@ -10,7 +11,7 @@  import           Codec.Picture import           Codec.Picture.Types-import           Control.Lens          ((^.))+import           Control.Lens          ((^.),(&)) import           Control.Monad import           Data.Monoid import           Data.Text             (Text)@@ -18,7 +19,7 @@ import           Graphics.SvgTree      hiding (Text,text) import           NeatInterpolation import           System.Random-import           System.Random.Shuffle+import "random-shuffle" System.Random.Shuffle   main :: IO ()@@ -147,7 +148,7 @@     mapM_ destroySprite sprites     -- Undraw equations     play $ drawAnimation' (Just 0xdeadbeef) 1 0.1 strokedSvg-      # reverseA+      & reverseA   where     glyphs = svgGlyphs svg     strokedSvg =@@ -189,7 +190,7 @@     fork $ do       wait (n*step)       play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)-        # applyE (overEnding fillDur $ fadeLineOutE sWidth))+        & applyE (overEnding fillDur $ fadeLineOutE sWidth))     fork $ do       wait (n*step+(1-fillDur))       newSprite $ do
examples/voice_advanced.hs view
@@ -7,7 +7,6 @@ import           Control.Monad import qualified Data.Text                     as T import           Reanimate-import           Reanimate.Ease import           Reanimate.Voice import           Reanimate.Builtin.Documentation import           Geom2D.CubicBezier                       ( QuadBezier(..)
examples/voice_triggers.hs view
@@ -1,15 +1,16 @@ #!/usr/bin/env stack -- stack runghc --package reanimate+{-# LANGUAGE ApplicativeDo     #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ApplicativeDo #-} module Main where +import           Control.Lens import           Control.Monad-import qualified Data.Text                     as T+import qualified Data.Text                       as T+import           Graphics.SvgTree                (ElementRef (..)) import           Reanimate-import           Reanimate.Voice import           Reanimate.Builtin.Documentation-import           Graphics.SvgTree                         ( ElementRef(..) )+import           Reanimate.Voice  transcript :: Transcript transcript = loadTranscript "voice_triggers.txt"@@ -53,8 +54,8 @@             translate (screenWidth / 4) 0 $ scale t $ scaleToHeight 4 $ center               img           )-      # signalA (bellS 2)-      # setDuration dur+      & signalA (bellS 2)+      & setDuration dur  {-# INLINE masked #-} masked :: String -> Double -> SVG -> SVG -> SVG -> SVG
reanimate.cabal view
@@ -3,7 +3,7 @@ --  see http://haskell.org/cabal/users-guide/  name:                reanimate-version:             0.4.1.0+version:             0.4.2.0 -- synopsis: -- description: license:             PublicDomain@@ -11,7 +11,7 @@ maintainer:          lemmih@gmail.com category:            Graphics synopsis:            Animation library based on SVGs.-homepage:            https://github.com/reanimate/reanimate#readme+homepage:            https://reanimate.github.io bug-reports:         https://github.com/reanimate/reanimate/issues build-type:          Simple extra-source-files:  ChangeLog.md, examples/*.hs, examples/*.golden@@ -37,23 +37,6 @@     Type:      git     Location:  git://github.com/lemmih/reanimate.git --- If both 'enable-hmatrix' and 'disable-hmatrix' are false, then use--- hmatrix on Macos and Linux but not Windows.-Flag enable-hmatrix-  Description: Enable hmatrix dependency on Windows (requires openblas)-  Default:     False-  Manual:      True--Flag disable-hmatrix-  Description: Force disable hmatrix dependency-  Default:     False-  Manual:      True--Flag test-  Description: Enable testing (requires stack)-  Default:     False-  Manual:      True- library   hs-source-dirs:     src   default-language:   Haskell2010@@ -68,24 +51,15 @@                       Reanimate.Svg.Constructors                       Reanimate.Svg.BoundingBox                       Reanimate.Svg.LineCommand-                      -- Reanimate.Diagrams                       Reanimate.Transform-                      Reanimate.Misc+                      Reanimate.Math.Triangulate                       Reanimate.Math.Common                       Reanimate.Math.Polygon-                      Reanimate.Math.Triangulate-                      Reanimate.Math.EarClip-                      Reanimate.Math.Visibility                       Reanimate.Math.SSSP                       Reanimate.Math.Balloon-                      Reanimate.Math.Compatible-                      Reanimate.Math.Render                       Reanimate.Morph.Common                       Reanimate.Morph.Linear                       Reanimate.Morph.Rotational-                      Reanimate.Morph.LeastWork-                      Reanimate.Morph.LineBend-                      Reanimate.Morph.Cache                       Reanimate.Raster                       Reanimate.ColorComponents                       Reanimate.ColorMap@@ -102,38 +76,67 @@                       Reanimate.Builtin.Slide                       Reanimate.Constants                       Reanimate.Parameters-                      Reanimate.Chiphunk                       Reanimate.PolyShape                       Reanimate.GeoProjection                       Reanimate.Builtin.Documentation                       Reanimate.Builtin.Images                       Reanimate.Debug                       Reanimate.Voice+                      Geom2D.CubicBezier.Linear   other-modules:      Reanimate.Cache+                      Reanimate.Morph.Cache                       Reanimate.Driver                       Reanimate.Driver.Check                       Reanimate.Driver.CLI                       Reanimate.Driver.Magick                       Reanimate.Driver.Server                       Reanimate.Driver.Compile+                      Reanimate.Misc                       Paths_reanimate-  build-depends:       base >=4.10 && <5,-                       time, text, filepath, process, directory,-                       containers, reanimate-svg >= 0.10.0.0, xml, bytestring, lens, linear, mtl, matrix,-                       JuicyPixels >= 3.3.3, attoparsec, parallel,-                       cubicbezier, websockets >= 0.12.7.0,-                       hashable, fsnotify, open-browser, random, random-shuffle, base64-bytestring,-                       vector >= 0.12.0.0, colour, cassava, ansi-terminal, temporary,-                       optparse-applicative, chiphunk >= 0.1.2.1,-                       geojson, aeson >= 1.3.0.0, earcut >= 0.1.0.4,-                       split, array, cereal, vector-space,-                       neat-interpolation >= 0.3-  if (flag(enable-hmatrix) || !os(windows)) && !flag(disable-hmatrix) {-    build-depends: hmatrix >= 0.20.0.0-    exposed-modules:  Reanimate.Morph.Rigid-                      Reanimate.Math.Smooth-                      Reanimate.Math.DCEL-  }+                      Reanimate.Math.Visibility+  build-depends:+    base                 >=4.10 && <5,+    JuicyPixels          >=3.3.3,+    aeson                >=1.3.0.0,+    ansi-terminal        >=0.8.0.4,+    array                >=0.5.2.0,+    attoparsec           >=0.13.2.0,+    base64-bytestring    >=1.0.0.1,+    bytestring           >=0.10.8.0,+    cassava              >=0.5.1.0,+    cereal               >=0.5.5.0,+    colour               >=2.3.4,+    containers           >=0.5.11.0,+    cubicbezier          >=0.6.0.5,+    directory            >=1.3.1.0,+    filelock             >=0.1.1.2,+    filepath             >=1.4.2,+    fsnotify             >=0.3.0.1,+    geojson              >=3.0.4,+    ghcid                >=0.7,+    hashable             >=1.2.7.0,+    hgeometry            >=0.11.0.0,+    hgeometry-combinatorial >=0.11.0.0,+    lens                 >=4.16.1,+    linear               >=1.20.8,+    matrix               >=0.3.6.1,+    mtl                  >=2.2.2,+    neat-interpolation   >=0.3,+    open-browser         >=0.2.1.0,+    optparse-applicative >=0.14.2.0,+    parallel             >=3.2.1.0,+    process              >=1.6.3.0,+    random               >=1.1,+    random-shuffle       >=0.0.4,+    reanimate-svg        >=0.10.0.0,+    split                >=0.2.3.3,+    temporary            >=1.3,+    text                 >=1.2.3.0,+    time                 >=1.8.0.0,+    vector               >=0.12.0.0,+    vector-space         >=0.13,+    websockets           >=0.12.7.0,+    xml                  >=1.3.14   ghc-options: -Wall -fno-ignore-asserts  test-suite spec@@ -145,16 +148,15 @@     Properties     Helpers   hs-source-dirs: test-  if !flag(test) {-    Buildable: False-  }   ghc-options: -w   build-depends:     base,     directory,     filepath,     bytestring, process,-    reanimate, vector, linear,+    reanimate,+    vector, linear,     QuickCheck >= 2.1.0, text,+    temporary,     tasty, tasty-golden, tasty-hunit,     tasty-quickcheck, tasty-rerun >= 1.1.17
+ src/Geom2D/CubicBezier/Linear.hs view
@@ -0,0 +1,344 @@+{-# LANGUAGE DeriveFoldable         #-}+{-# LANGUAGE DeriveFunctor          #-}+{-# LANGUAGE DeriveTraversable      #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses  #-}+{-# LANGUAGE UndecidableInstances   #-}+{-|+Module      : Geom2D.CubicBezier.Linear+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Convenience wrapper around 'Geom2D.CubicBezier'++-}+module Geom2D.CubicBezier.Linear+  ( AnyBezier(..)+  , CubicBezier(..)+  , QuadBezier(..)+  , OpenPath(..)+  , ClosedPath(..)+  , PathJoin(..)+  , ClosedMetaPath(..)+  , OpenMetaPath(..)+  , MetaJoin(..)+  , MetaNodeType(..)+  , FillRule(..)+  , Tension(..)+  , quadToCubic+  , arcLength+  , arcLengthParam+  , C.splitBezier+  , colinear+  , evalBezier+  , evalBezierDeriv+  , bezierHoriz+  , bezierVert+  , C.bezierSubsegment+  , C.reorient+  , closedPathCurves+  , openPathCurves+  , curvesToClosed+  , closest+  , unmetaOpen+  , unmetaClosed+  , union+  , bezierIntersection+  , interpolateVector+  , vectorDistance+  , findBezierInflection+  , findBezierCusp+  ) where++import qualified Data.Vector.Unboxed as V+import qualified Geom2D.CubicBezier  as C+import           Graphics.SvgTree    (FillRule (..))+import           Linear.V2++------------------------------------------------------------+-- Data types++-- | A bezier curve of any degree.+newtype AnyBezier a = AnyBezier (V.Vector (V2 a))++-- | A cubic bezier curve.+data CubicBezier a = CubicBezier+  { cubicC0 :: !(V2 a)+  , cubicC1 :: !(V2 a)+  , cubicC2 :: !(V2 a)+  , cubicC3 :: !(V2 a)+  } deriving (Show, Eq)++-- | A quadratic bezier curve.+data QuadBezier a = QuadBezier+  { quadC0 :: !(V2 a)+  , quadC1 :: !(V2 a)+  , quadC2 :: !(V2 a)+  } deriving (Show, Eq)++-- | Open cubicbezier path.+data OpenPath a = OpenPath [(V2 a, PathJoin a)] (V2 a)+  deriving (Show, Eq)++-- | Closed cubicbezier path.+data ClosedPath a = ClosedPath [(V2 a, PathJoin a)]+  deriving (Show, Eq)++-- | Join two points with either a straight line or a bezier+--   curve with two control points.+data PathJoin a+  = JoinLine+  | JoinCurve (V2 a) (V2 a)+  deriving (Show, Eq)++-- | Closed meta path.+data ClosedMetaPath a = ClosedMetaPath [(V2 a, MetaJoin a)]+  deriving (Show, Eq)++-- | Open meta path+data OpenMetaPath a = OpenMetaPath [(V2 a, MetaJoin a)] (V2 a)+  deriving (Show, Eq)++-- | The tension value specifies how /tense/ the curve is.+--   A higher value means the curve approaches a line segment,+--   while a lower value means the curve is more round. Metafont+--   doesn't allow values below 3/4.+data Tension a+  = Tension+  { tensionValue :: a }+  | TensionAtLeast -- ^ Like Tension, but keep the segment inside the+                   --   bounding triangle defined by the control points,+                   --   if there is one.+  { tensionValue :: a }+  deriving (Functor, Foldable, Traversable, Eq, Show)++-- | Join two meta points with either a bezier curve or tension+--   contraints.+data MetaJoin a+  = MetaJoin+  { metaTypeL :: MetaNodeType a+  , tensionL  :: Tension a+  , tensionR  :: Tension a+  , metaTypeR :: MetaNodeType a+  }+  | Controls (V2 a) (V2 a)+  deriving (Show, Eq)++-- | Node constraint type.+data MetaNodeType a+  = Open+  | Curl { curlgamma :: a }+  | Direction { nodedir :: V2 a }+  deriving (Show, Eq)++------------------------------------------------------------+-- Methods++-- | Convert a quadratic bezier to a cubic bezier.+quadToCubic :: Fractional a => QuadBezier a -> CubicBezier a+quadToCubic = upCast . C.quadToCubic . downCast++-- | @arcLength c t tol@ finds the arclength of the bezier @c@ at @t@,+--   within given tolerance @tol@.+arcLength :: CubicBezier Double -> Double -> Double -> Double+arcLength bezier t tol = C.arcLength (downCast bezier) t tol++-- | @arcLengthParam c len tol@ finds the parameter where the curve @c@+--   has the arclength @len@, within tolerance @tol@.+arcLengthParam :: CubicBezier Double -> Double -> Double -> Double+arcLengthParam bezier t tol = C.arcLengthParam (downCast bezier) t tol++-- | Return @False@ if some points fall outside a line with a thickness of the given tolerance.+colinear :: CubicBezier Double -> Double -> Bool+colinear bezier tol = C.colinear (downCast bezier) tol++-- | Calculate a value on the bezier curve.+evalBezier :: (C.GenericBezier b, V.Unbox a, Fractional a) => b a -> a -> V2 a+evalBezier c p = upCast $ C.evalBezier c p++-- | Calculate a value and the first derivative on the curve.+evalBezierDeriv :: (V.Unbox a, Fractional a,C.GenericBezier b) => b a -> a -> (V2 a, V2 a)+evalBezierDeriv c p = upCast $ C.evalBezierDeriv c p++-- | Find the parameter where the bezier curve is horizontal.+bezierHoriz :: CubicBezier Double -> [Double]+bezierHoriz = C.bezierHoriz . downCast++-- | Find the parameter where the bezier curve is vertical.+bezierVert :: CubicBezier Double -> [Double]+bezierVert = C.bezierVert . downCast++-- | Create a normal path from a metapath.+unmetaOpen :: OpenMetaPath Double -> OpenPath Double+unmetaOpen = upCast . C.unmetaOpen . downCast++-- | Create a normal path from a metapath.+unmetaClosed :: ClosedMetaPath Double -> ClosedPath Double+unmetaClosed = upCast . C.unmetaClosed . downCast++-- | `O((n+m)*log(n+m))`, for n segments and m intersections.+--   Union of paths, removing overlap and rounding to the given tolerance.+union :: [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]+union p fill tol = upCast (C.union (downCast p) (downCast fill) tol)++-- | Find the intersections between two Bezier curves, using the Bezier Clip algorithm.+--   Returns the parameters for both curves.+bezierIntersection :: CubicBezier Double -> CubicBezier Double -> Double -> [(Double, Double)]+bezierIntersection a b t = C.bezierIntersection (downCast a) (downCast b) t++-- | Find the closest value on the bezier to the given point, within tolerance.+--   Return the first value found.+closest :: CubicBezier Double -> V2 Double -> Double -> Double+closest c p t = C.closest (downCast c) (downCast p) t++-- | Return the closed path as a list of curves.+closedPathCurves :: Fractional a => ClosedPath a -> [CubicBezier a]+closedPathCurves = upCast . C.closedPathCurves . downCast++-- | Return the open path as a list of curves.+openPathCurves :: Fractional a => OpenPath a -> [CubicBezier a]+openPathCurves = upCast . C.openPathCurves . downCast++-- | Make an open path from a list of curves. The last control point of each curve is ignored.+curvesToClosed :: [CubicBezier a] -> ClosedPath a+curvesToClosed = upCast . C.curvesToClosed . downCast++-- | Interpolate between two vectors.+interpolateVector :: Num a => V2 a -> V2 a -> a -> V2 a+interpolateVector a b p = upCast $ C.interpolateVector (downCast a) (downCast b) p++-- | Distance between two vectors.+vectorDistance :: Floating a => V2 a -> V2 a -> a+vectorDistance a b = C.vectorDistance (downCast a) (downCast b)++-- | Find inflection points on the curve.+findBezierInflection :: CubicBezier Double -> [Double]+findBezierInflection = C.findBezierInflection . downCast++-- | Find the cusps of a bezier.+findBezierCusp :: CubicBezier Double -> [Double]+findBezierCusp = C.findBezierCusp . downCast++------------------------------------------------------------+-- Instances++instance C.GenericBezier QuadBezier where+  degree = C.degree . downCast+  toVector = C.toVector . downCast+  unsafeFromVector = upCast . C.unsafeFromVector++instance C.GenericBezier CubicBezier where+  degree = C.degree . downCast+  toVector = C.toVector . downCast+  unsafeFromVector = upCast . C.unsafeFromVector++instance C.GenericBezier AnyBezier where+  degree = C.degree . downCast+  toVector = C.toVector . downCast+  unsafeFromVector = upCast . C.unsafeFromVector++------------------------------------------------------------+-- Casting++class Cast a b | a -> b, b -> a where+  downCast :: a -> b+  upCast   :: b -> a++instance Cast a b => Cast [a] [b] where+  downCast = map downCast+  upCast = map upCast++instance (Cast a a', Cast b b') => Cast (a,b) (a',b') where+  downCast (a, b) = (downCast a, downCast b)+  upCast (a, b) = (upCast a, upCast b)++instance Cast (V2 a) (C.Point a) where+  downCast (V2 a b) = C.Point a b+  upCast (C.Point a b) = V2 a b++instance Cast FillRule C.FillRule where+  downCast FillEvenOdd = C.EvenOdd+  downCast FillNonZero = C.NonZero+  upCast C.EvenOdd = FillEvenOdd+  upCast C.NonZero = FillNonZero++instance Cast (CubicBezier a) (C.CubicBezier a) where+  downCast (CubicBezier a b c d) = C.CubicBezier+    (downCast a) (downCast b) (downCast c) (downCast d)+  upCast (C.CubicBezier a b c d) = CubicBezier+    (upCast a) (upCast b) (upCast c) (upCast d)++instance Cast (QuadBezier a) (C.QuadBezier a) where+  downCast (QuadBezier a b c) = C.QuadBezier+    (downCast a) (downCast b) (downCast c)+  upCast (C.QuadBezier a b c)= QuadBezier+    (upCast a) (upCast b) (upCast c)++instance V.Unbox a => Cast (AnyBezier a) (C.AnyBezier a) where+  downCast (AnyBezier arr) = C.AnyBezier $+    V.map (\(V2 a b) -> (a,b)) arr+  upCast (C.AnyBezier arr) = AnyBezier $+    V.map (\(a, b) -> V2 a b) arr++instance Cast (MetaNodeType a) (C.MetaNodeType a) where+  downCast Open            = C.Open+  downCast (Curl gamma)    = C.Curl gamma+  downCast (Direction dir) = C.Direction (downCast dir)+  upCast C.Open            = Open+  upCast (C.Curl gamma)    = Curl gamma+  upCast (C.Direction dir) = Direction (upCast dir)++instance Cast (Tension a) (C.Tension a) where+  downCast (Tension v)        = C.Tension v+  downCast (TensionAtLeast v) = C.TensionAtLeast v+  upCast (C.Tension v)        = Tension v+  upCast (C.TensionAtLeast v) = TensionAtLeast v++instance Cast (MetaJoin a) (C.MetaJoin a) where+  downCast (MetaJoin tyL tL tR tyR) =+    C.MetaJoin (downCast tyL) (downCast tL) (downCast tR) (downCast tyR)+  downCast (Controls p1 p2) = C.Controls (downCast p1) (downCast p2)+  upCast (C.MetaJoin tyL tL tR tyR) =+    MetaJoin (upCast tyL) (upCast tL) (upCast tR) (upCast tyR)+  upCast (C.Controls p1 p2)         = Controls (upCast p1) (upCast p2)++instance Cast (PathJoin a) (C.PathJoin a) where+  downCast JoinLine        = C.JoinLine+  downCast (JoinCurve a b) = C.JoinCurve (downCast a) (downCast b)+  upCast C.JoinLine        = JoinLine+  upCast (C.JoinCurve a b) = JoinCurve (upCast a) (upCast b)++instance Cast (OpenMetaPath a) (C.OpenMetaPath a) where+  downCast (OpenMetaPath lst end) = C.OpenMetaPath+    [ (downCast p, downCast j)+    | (p, j) <- lst ] (downCast end)+  upCast (C.OpenMetaPath lst end) = OpenMetaPath+    [ (upCast p, upCast j)+    | (p, j) <- lst ] (upCast end)++instance Cast (ClosedMetaPath a) (C.ClosedMetaPath a) where+  downCast (ClosedMetaPath lst) = C.ClosedMetaPath+    [ (downCast p, downCast j)+    | (p, j) <- lst ]+  upCast (C.ClosedMetaPath lst) = ClosedMetaPath+    [ (upCast p, upCast j)+    | (p, j) <- lst ]++instance Cast (OpenPath a) (C.OpenPath a) where+  downCast (OpenPath lst end) = C.OpenPath+    [ (downCast p, downCast j)+    | (p, j) <- lst ] (downCast end)+  upCast (C.OpenPath lst end) = OpenPath+    [ (upCast p, upCast j)+    | (p, j) <- lst ] (upCast end)++instance Cast (ClosedPath a) (C.ClosedPath a) where+  downCast (ClosedPath lst) = C.ClosedPath+    [ (downCast p, downCast j)+    | (p, j) <- lst ]+  upCast (C.ClosedPath lst) = ClosedPath+    [ (upCast p, upCast j)+    | (p, j) <- lst ]
src/Reanimate.hs view
@@ -8,8 +8,8 @@ Portability : POSIX  Reanimate is an animation library based on SVGs. It is designed to act like glue-between external components such as 'latex', 'ffmpeg', 'gnuplot', 'diagrams',-and 'povray'.+between external components such as \'latex\', \'ffmpeg\', \'gnuplot\', \'diagrams\',+and \'povray\'.  = Canvas @@ -39,7 +39,7 @@     SVG,     Time,     Duration,-    Animation(..),+    Animation,     mkAnimation,     animate,     staticFrame,@@ -70,12 +70,11 @@     fromToS,     reverseS,     curveS,+    powerS,     bellS,     oscillateS,-    fromListS,     cubicBezierS,     -- ** Scenes-    (#),     Scene   , ZIndex   , sceneAnimation    -- :: (forall s. Scene s a) -> Animation@@ -116,7 +115,8 @@   , spriteTween       -- :: Sprite s -> Duration -> (Double -> SVG -> SVG) -> Scene s ()   , spriteVar         -- :: Sprite s -> a -> (a -> SVG -> SVG) -> Scene s (Var s a)   , spriteE           -- :: Sprite s -> Effect -> Scene s ()-  , spriteZ,          -- :: Sprite s -> ZIndex -> Scene s ()+  , spriteZ           -- :: Sprite s -> ZIndex -> Scene s ()+  , spriteScope,      -- :: Scene s a -> Scene s a      -- ** Effects     Effect,
src/Reanimate/Animation.hs view
@@ -1,8 +1,20 @@+{-|+Module      : Reanimate.Animation+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Declarative animation API based on combinators. For a higher-level interface,+see 'Reanimate.Scene'.++-} module Reanimate.Animation   ( Duration   , Time   , SVG-  , Animation(..) -- TODO should this be exposed? The constructor is used directly in Effect.hs+  , Animation   -- * Creating animations   , mkAnimation   , animate@@ -27,7 +39,6 @@   , pauseAtEnd   , pauseAtBeginning   , pauseAround-  , pauseUntil   , repeatA   , reverseA   , playThenReverseA@@ -35,7 +46,6 @@   , freezeAtPercentage   , addStatic   -- * Misc-  , (#)   , getAnimationFrame   , Sync(..)   -- * Rendering@@ -60,11 +70,13 @@ -- | Time signal. Goes from 0 to 1, inclusive. type Time = Double +-- | SVG node. type SVG = Tree  -- | Animations are SVGs over a finite time. data Animation = Animation Duration (Time -> SVG) +-- | Construct an animation with a given duration. mkAnimation :: Duration -> (Time -> SVG) -> Animation mkAnimation = Animation @@ -183,9 +195,11 @@   where     t' = clamp 0 1 (t/d) +-- | Helper function for pretty-printing SVG nodes. renderTree :: SVG -> String renderTree t = maybe "" ppElement $ xmlOfTree t +-- | Helper function for pretty-printing SVG nodes as SVG documents. renderSvg :: Maybe Number -- ^ The number to use as value of the @width@ attribute of the resulting top-level svg element. If @Nothing@, the width attribute won't be rendered.           -> Maybe Number -- ^ Similar to previous argument, but for @height@ attribute.           -> SVG          -- ^ SVG to render@@ -247,11 +261,6 @@ pauseAround :: Duration -> Duration -> Animation -> Animation pauseAround start end = pauseAtEnd end . pauseAtBeginning start --- XXX: Rename to 'setDurationFreeze'. Add 'setDurationDrop' and---      'setDurationLoop'.-pauseUntil :: Duration -> Animation -> Animation-pauseUntil d a = pauseAtEnd (d-duration a) a- -- Freeze frame at time @t@. freezeFrame :: Time -> Animation -> (Time -> SVG) freezeFrame t (Animation d f) = const $ f (t/d)@@ -354,6 +363,7 @@   where     len' = d - clamp 0 d len +-- | @lastA duration animation@ return the last @duration@ seconds of the animation. lastA :: Duration -> Animation -> Animation lastA len a = dropA (duration a - len) a @@ -362,9 +372,10 @@   | a < b     = max a (min b number)   | otherwise = max b (min a number) -(#) :: a -> (a -> b) -> b-o # f = f o+-- (#) :: a -> (a -> b) -> b+-- o # f = f o +-- | Ask for an animation frame using a given synchronization policy. getAnimationFrame :: Sync -> Animation -> Time -> Duration -> SVG getAnimationFrame sync (Animation aDur aGen) t d =   case sync of@@ -375,6 +386,7 @@   where     takeFrac f = snd (properFraction f :: (Int, Double)) +-- | Animation synchronization policies. data Sync   = SyncStretch   | SyncLoop
src/Reanimate/Blender.hs view
@@ -1,40 +1,73 @@ {-# LANGUAGE OverloadedStrings #-}+{-|+  [Blender](https://www.blender.org/) is a free and open-source 3D graphics toolkit.+  It is usually used through a graphical user-interface but can also be+  scripted via Python. These Blender scripts can access 100% of Blender's+  functionality and offer a convenient way of coding 3D effects.++  Running Blender can be time-consuming but heavy caching means scripts+  are only run when they change.++  Blender cheatsheet:++> # To generate with a transparent background, set film_transparent = True:+> bpy.context.scene.render.film_transparent = True+>+> # Filmic is great for photorealism but bad for animations.+> # If you want your textures to keep their exact color values,+> # set the view_transform to 'Standard':+> bpy.context.scene.view_settings.view_transform = 'Standard'+>+> # Blender's default render engine is 'EEVEE', fast but not a raytracer.+> # To switch to raytracing, set the engine to 'CYCLES':+> bpy.context.scene.render.engine = 'CYCLES'+>+> # Rendering at full resolution can be slow. When developing, try+> # decreasing the resolution_percentage for faster renders.+> bpy.context.scene.render.resolution_percentage = 10+>+> # The resolution of the final image are set by resolution_x and resolution_y:+> bpy.context.scene.render.resolution_x = 320+> bpy.context.scene.render.resolution_y = 180++-} module Reanimate.Blender   ( blender   , blender'   ) where -import           Codec.Picture.Png-import qualified Data.ByteString            as B+import           Data.Hashable import           Data.Text                  (Text)--- import qualified Data.Text                  as T import qualified Data.Text.IO               as T import           Graphics.SvgTree           (Tree (..))+import           Reanimate.Animation import           Reanimate.Cache+import           Reanimate.Constants import           Reanimate.Misc-import           Reanimate.Raster import           Reanimate.Parameters-import           Reanimate.Animation+import           Reanimate.Raster import           Reanimate.Svg.Constructors import           System.FilePath            (replaceExtension, (<.>)) import           System.IO.Unsafe           (unsafePerformIO)-import           Data.Hashable +-- | Run a Blender script and embed the resulting image file. The+--   image will be scaled to fit the screen exactly (assuming a default+--   canvas layout). Note that Blender resolution defaults to 1920x1080+--   but can be changed in the script code. blender :: Text -> SVG blender script =   unsafePerformIO $ mkBlenderImage script +-- | Generate Blender image as a separate PNG file. Can be embedded with+--   `mkImage`. blender' :: Text -> FilePath blender' script =   unsafePerformIO $ mkBlenderImage' script  mkBlenderImage :: Text -> IO Tree mkBlenderImage script | pNoExternals = pure $ mkText script-mkBlenderImage script = do-    png <- B.readFile =<< mkBlenderImage' script-    case decodePng png of-      Left{}    -> error "bad image"-      Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img+mkBlenderImage script =+  mkImage screenWidth screenHeight <$> mkBlenderImage' script  mkBlenderImage' :: Text -> IO FilePath mkBlenderImage' _ | pNoExternals = pure "/blender/has/been/disabled"@@ -46,4 +79,4 @@                 , "--python-exit-code", "1"                 , "--render-output", target, "--python", py_file]   where-    template = show (hash script) <.> "png"+    template = encodeInt (hash script) <.> "png"
src/Reanimate/Builtin/CirclePlot.hs view
@@ -1,3 +1,14 @@+{-|+Module      : Reanimate.Builtin.CirclePlot+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Convenience module for rendering circle plots.++-} module Reanimate.Builtin.CirclePlot where  import           Codec.Picture
src/Reanimate/Builtin/Documentation.hs view
@@ -1,3 +1,15 @@+{-|+Module      : Reanimate.Builtin.Documentation+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++This module contains convenience functions used in documention+GIFs for a consistent look and feel.++-} module Reanimate.Builtin.Documentation where  import Reanimate.Animation@@ -6,6 +18,7 @@ import Reanimate.Constants import Codec.Picture +-- | Default environment for API documentation GIFs. docEnv :: Animation -> Animation docEnv = mapA $ \svg -> mkGroup   [ mkBackground "white"@@ -25,11 +38,6 @@   partialSvg t $ pathify $   mkCircle (screenHeight/3) -drawBall :: Animation-drawBall = mkAnimation 2 $ \t ->-  scale t $ withFillOpacity 1 $ withFillColor "red" $-  mkCircle (screenHeight/3)- -- | <<docs/gifs/doc_drawProgress.gif>> drawProgress :: Animation drawProgress = mkAnimation 2 $ \t ->@@ -41,6 +49,7 @@   where     widthP = 0.8 +-- | Render a full-screen view of a color-map. showColorMap :: (Double -> PixelRGB8) -> SVG showColorMap f = center $ scaleToSize screenWidth screenHeight $ embedImage img   where@@ -49,5 +58,6 @@     img = generateImage pixelRenderer width height     pixelRenderer x _y = f (fromIntegral x / fromIntegral (width-1)) +-- | Default background color for videos on reanimate.rtfd.io rtfdBackgroundColor :: PixelRGBA8 rtfdBackgroundColor = PixelRGBA8 252 252 252 0xFF
src/Reanimate/Builtin/Flip.hs view
@@ -3,11 +3,17 @@ {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE ApplicativeDo     #-} {- HLINT ignore -}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Builtin.Flip   ( FlipSprite(..)   , flipSprite   , flipTransition-  , flipTransitionOpts   ) where  import           Reanimate.Animation@@ -21,6 +27,7 @@ import           NeatInterpolation (text) import qualified Data.Text           as T +-- | Control structure with parameters for the blender script. data FlipSprite s = FlipSprite   { fsSprite :: Sprite s   , fsBend   :: Var s Double@@ -28,6 +35,8 @@   , fsWobble :: Var s Double   } +-- | Project two animations on each side of a plane and flip the plane+--   upside down. flipSprite :: Animation -> Animation -> Scene s (FlipSprite s) flipSprite front back = do     bend <- newVar 0@@ -61,6 +70,7 @@   where     dur = max (duration a) (duration b) +-- | 3D flip transition. flipTransition :: Transition flipTransition = flipTransitionOpts bend zoom wobble   where
src/Reanimate/Builtin/Images.hs view
@@ -1,3 +1,14 @@+{-|+Module      : Reanimate.Builtin.Images+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Collection of built-in images.++-} module Reanimate.Builtin.Images   ( svgLogo   , haskellLogo@@ -46,6 +57,7 @@ githubIcon = unsafePerformIO $ embedImage "data/github-icon.svg"  {-# NOINLINE githubWhiteIcon #-}+-- | <<docs/gifs/doc_githubWhiteIcon.gif>> githubWhiteIcon :: SVG githubWhiteIcon = unsafePerformIO $ embedImage "data/github-icon-white.svg" 
src/Reanimate/Builtin/Slide.hs view
@@ -1,3 +1,10 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Builtin.Slide where  import Reanimate.Transition@@ -13,6 +20,7 @@     moveRight = constE (translate screenWidth 0)     andE a b d t = a d t . b d t +-- | <<docs/gifs/doc_slideDownT.gif>> slideDownT :: Transition slideDownT = effectT slideDown (andE slideDown moveUp)   where@@ -20,6 +28,7 @@     moveUp = constE (translate 0 screenHeight)     andE a b d t = a d t . b d t +-- | <<docs/gifs/doc_slideUpT.gif>> slideUpT :: Transition slideUpT = effectT slideUp (andE slideUp moveDown)   where
src/Reanimate/Builtin/TernaryPlot.hs view
@@ -1,19 +1,56 @@-module Reanimate.Builtin.TernaryPlot where+{-|+Module      : Reanimate.Builtin.TernaryPlot+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX +Implementation of ternary plots: <https://en.wikipedia.org/wiki/Ternary_plot>++-}+module Reanimate.Builtin.TernaryPlot+  ( ACoord+  , BCoord+  , CCoord+  , ternaryPlot+  -- , atCenter+  -- , radius+  , toCartesianCoords+  , toOffsetCartesianCoords+  , fromCartesianCoords+  ) where+ import           Codec.Picture import           Graphics.SvgTree (Tree) import           Reanimate.Raster import           Reanimate.Svg --- (top, left, right) -- a+b+c=1+-- | Left-most coordinate. type ACoord = Double+-- | Top-most coordinate. type BCoord = Double+-- | Right-most coordinate. type CCoord = Double --- Creates a centered ternary plot with a width of 5.-ternaryPlot :: Int -- ^ Pixels in the X-axis.-            -> (ACoord -> BCoord -> CCoord -> PixelRGBA8) -> Tree+-- | Creates a centered ternary plot with a width of 5.+--+--   Example:+--+--   > ternaryPlot 100 $ \aCoord bCoord cCoord -> promotePixel $+--   >   let red   = round $ aCoord*255+--   >       green = round $ bCoord*255+--   >       blue  = round $ cCoord*255+--   >   in PixelRGB8 red green blue+--+--   <<docs/gifs/doc_ternaryPlot.gif>>+ternaryPlot :: Int -- ^ Pixels in the X-axis. More pixels => higher quality.+            -> (ACoord -> BCoord -> CCoord -> PixelRGBA8)+            -- ^ a+b+c=1. A=1 is the left-most position,+            --   B=1 is the top-most position, and+            --   C=1 is the right-most position.+            -> Tree ternaryPlot density fn =     scaleToWidth stdWidth $     translate (-cX) (-cY) $@@ -36,22 +73,27 @@         then PixelRGBA8 0 0 0 0         else fn aCoord bCoord cCoord -atCenter :: Double -> Tree -> Tree-atCenter stdWidth = translate (-cX*stdWidth) (-cY*stdWidth)-  where-    (cX, cY) = toCartesianCoords (1/3) (1/3)+-- atCenter :: Double -> Tree -> Tree+-- atCenter stdWidth = translate (-cX*stdWidth) (-cY*stdWidth)+--   where+--     (cX, cY) = toCartesianCoords (1/3) (1/3) -radius :: Double-radius = sqrt (cX*cX + cY*cY)-  where-    (cX, cY) = toCartesianCoords (1/3) (1/3)+-- radius :: Double+-- radius = sqrt (cX*cX + cY*cY)+--   where+--     (cX, cY) = toCartesianCoords (1/3) (1/3) +-- | Compute the XY coordinates from ternary coordinates.+--   Note that @CCoord@ is given because @a+b+c=1@. toCartesianCoords :: ACoord -> BCoord -> (Double, Double) toCartesianCoords a b = (x, y)   where     x = (a+2*b)/2     y = (sqrt 3 / 2) * a +-- | Compute the XY coordinates relative from the center of the+--   ternary plot.+--   Note that @CCoord@ is given because @a+b+c=1@. toOffsetCartesianCoords :: ACoord -> BCoord -> (Double, Double) toOffsetCartesianCoords a b =     (tx-zx, ty-zy)@@ -59,6 +101,7 @@     (zx,zy) = toCartesianCoords (1/3) (1/3)     (tx,ty) = toCartesianCoords a b +-- | Compute ternary coordinates from XY coordinates. fromCartesianCoords :: Double -> Double -> (ACoord, BCoord, CCoord) fromCartesianCoords x y = (a,b,1-a-b)   where
− src/Reanimate/Chiphunk.hs
@@ -1,97 +0,0 @@-module Reanimate.Chiphunk-  ( simulate-  , BodyStore-  , newBodyStore-  , addToBodyStore-  , spaceFreeRecursive-  , polyShapesToBody-  , polygonsToBody-  ) where--import           Chiphunk.Low-import           Control.Monad-import           Data.IORef-import           Data.Map            (Map)-import qualified Data.Map            as Map-import qualified Data.Vector         as V-import qualified Data.Vector.Mutable as V-import           Foreign.Ptr-import           Graphics.SvgTree    (Tree)-import           Linear.V2 (V2(..))-import           Reanimate.Animation-import           Reanimate.PolyShape-import           Reanimate.Svg.Constructors--type BodyStore = IORef (Map WordPtr Tree)--newBodyStore :: IO BodyStore-newBodyStore = newIORef Map.empty--addToBodyStore :: BodyStore -> Body -> Tree -> IO ()-addToBodyStore store body svg = do-  key <- atomicModifyIORef' store $ \m ->-          case Map.maxViewWithKey m of-            Nothing -> (Map.singleton 1 svg, 1)-            Just ((maxKey,_),_) ->-              (Map.insert (maxKey+1) svg m, maxKey+1)-  bodyUserData body $= wordPtrToPtr key--renderBodyStore :: Space -> BodyStore -> IO Tree-renderBodyStore space store = do-  m <- readIORef store-  lst <- newIORef []-  spaceEachBody space (\body _dat -> do-    key <- get (bodyUserData body)-    case Map.lookup (ptrToWordPtr key) m of-      Nothing -> putStrLn "Body doesn't have an associated SVG"-      Just svg -> do-        Vect posX posY <- get $ bodyPosition body-        angle <- get $ bodyAngle body-        let bodySvg =-              translate posX posY $-              rotate (angle/pi*180)-              svg-        modifyIORef lst (bodySvg:)-    ) nullPtr-  result <- readIORef lst-  return $ mkGroup result---simulate :: Space -> BodyStore -> Double -> Int -> Double -> IO Animation-simulate space store fps stepsPerFrame dur = do-  let timeStep = 1/(fps*fromIntegral stepsPerFrame)-      frames = round (dur * fps)-  v <- V.new frames-  forM_ [0..frames-1] $ \nth -> do-    svg <- renderBodyStore space store-    V.write v nth svg-    replicateM_ stepsPerFrame $ spaceStep space timeStep-  frozen <- V.unsafeFreeze v-  return $ mkAnimation dur $ \t ->-    let key = round (t * fromIntegral (frames-1))-    in frozen V.! key--polyShapesToBody :: Space -> [PolyShape] -> IO Body-polyShapesToBody space poly =-    polygonsToBody space (map (map toVect) $ plDecompose poly)-  where-    toVect (V2 x y) = Vect x y--polygonsToBody :: Space -> [[Vect]] -> IO Body-polygonsToBody space polygons = do-  plBody <- bodyNew 0 0-  spaceAddBody space plBody--  forM_ polygons $ \vects -> do-    polyShape <- polyShapeNewRaw plBody vects 0.00-    shapeDensity polyShape $= 1-    spaceAddShape space polyShape-    shapeFriction polyShape $= 0.7-    shapeElasticity polyShape $= 0.5-  return plBody--spaceFreeRecursive :: Space -> IO ()-spaceFreeRecursive space = do-  spaceEachBody space (\body _ -> bodyFree body) nullPtr-  spaceEachShape space (\shape _ -> shapeFree shape) nullPtr-  spaceFree space
src/Reanimate/ColorComponents.hs view
@@ -1,6 +1,27 @@ {-# LANGUAGE RecordWildCards #-}-module Reanimate.ColorComponents where+{- |+  Colors are three dimensional and can be projected into many color spaces+  with different properties. +  Interpolating directly in the RGB color space is unintuitive and rarely useful.+  If you want to transition through color, you most likely want either the XYZ space+  (for physically accurate color transitions) or the LAB space (for esthetically+  pleasing colors).+-}+module Reanimate.ColorComponents+  ( ColorComponents(..)+  , rgbComponents+  , hsvComponents+  , labComponents+  , xyzComponents+  , lchComponents+  , interpolate+  , interpolateRGB8+  , interpolateRGBA8+  , toRGB8+  , fromRGB8+  ) where+ import           Codec.Picture import           Codec.Picture.Types import           Data.Colour@@ -12,9 +33,13 @@ import           Data.Fixed import           Reanimate.Ease +-- | Constructor and destructor for color's three components. data ColorComponents = ColorComponents   { colorUnpack :: Colour Double -> (Double, Double, Double)-  , colorPack   :: Double -> Double -> Double -> Colour Double }+    -- ^ Unpack a color into its three components.+  , colorPack   :: Double -> Double -> Double -> Colour Double+    -- ^ Restore a color from three coordinates.+  }  -- | > interpolate rgbComponents yellow blue --@@ -71,6 +96,7 @@     pack l c h =       cieLAB d65 l (cos (toRad h) * c) (sin (toRad h) * c) +-- | Smoothly interpolate between two colors using the given color components. interpolate :: ColorComponents -> Colour Double -> Colour Double -> (Double -> Colour Double) interpolate ColorComponents{..} from to = \d ->     colorPack (a1 + (a2-a1)*d) (b1 + (b2-b1)*d) (c1 + (c2-c1)*d)@@ -78,9 +104,11 @@     (a1,b1,c1) = colorUnpack from     (a2,b2,c2) = colorUnpack to +-- | Convenience interpolation function for RGB8 values. interpolateRGB8 :: ColorComponents -> PixelRGB8 -> PixelRGB8 -> (Double -> PixelRGB8) interpolateRGB8 comps from to = toRGB8 . interpolate comps (fromRGB8 from) (fromRGB8 to) +-- | Convenience interpolation function for RGBA8 values. interpolateRGBA8 :: ColorComponents -> PixelRGBA8 -> PixelRGBA8 -> (Double -> PixelRGBA8) interpolateRGBA8 comps from to = \t ->   case interp t of@@ -90,10 +118,12 @@   where     interp = interpolateRGB8 comps (dropTransparency from) (dropTransparency to) +-- | Convenience function for expressing a color as an RGB8 value. toRGB8 :: Colour Double -> PixelRGB8 toRGB8 c = PixelRGB8 r g b   where     RGB r g b = toSRGBBounded c +-- | Convenience function for expressing an RGB8 value as a color. fromRGB8 :: PixelRGB8 -> Colour Double fromRGB8 (PixelRGB8 r g b) = sRGB24 r g b
src/Reanimate/ColorMap.hs view
@@ -1,4 +1,9 @@ {-# LANGUAGE OverloadedStrings #-}+{-|+  A colormap takes a number between 0 and 1 (inclusive) and spits out a color.+  The colors do not have an alpha component but one can be added with+  `Codec.Picture.Types.promotePixel`.+-} module Reanimate.ColorMap   ( turbo   , viridis
src/Reanimate/ColorSpace.hs view
@@ -1,8 +1,20 @@-module Reanimate.ColorSpace where+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-}+module Reanimate.ColorSpace+  ( Nanometer+  , coneSensitivity+  , bigXYZCoordinates+  , lightXYZCoordinates+  , nmToColor+  ) where  import qualified Data.ByteString.Lazy       as BS import           Data.Colour.CIE-import           Data.Colour.CIE.Illuminant (d65) import           Data.Csv import           Data.Map                   (Map) import qualified Data.Map                   as Map@@ -11,12 +23,11 @@ import           Paths_reanimate import           System.IO.Unsafe -import           Graphics.SvgTree           (Tree)-import           Reanimate.Svg.Constructors-+-- | Wavelengths in nanometers. type Nanometer = Integer  {-# NOINLINE lightXYZCoordinates #-}+-- | (small) xyz values for each wavelength of light. lightXYZCoordinates :: Map Nanometer (Double, Double, Double) lightXYZCoordinates = unsafePerformIO $ do   dat <- BS.readFile =<< getDataFileName "data/CIExyz.csv"@@ -26,6 +37,7 @@       [ (nm, (x,y,z)) | (nm,x,y,z) <- V.toList vec, nm <= 700 ]  {-# NOINLINE bigXYZCoordinates #-}+-- | (big) XYZ values for each wavelength of light. bigXYZCoordinates :: Map Nanometer (Double, Double, Double) bigXYZCoordinates = unsafePerformIO $ do   dat <- BS.readFile =<< getDataFileName "data/CIE_XYZ.csv"@@ -34,34 +46,16 @@     Right vec -> return $ Map.fromList       [ (nm, (x,y,z)) | (nm,x,y,z) <- V.toList vec, nm <= 700 ] +-- | Helper function for converting a wavelength of light into the+--   perceived color. nmToColor :: Nanometer -> Maybe (Colour Double) nmToColor nm = do   (x, y, z) <- Map.lookup nm bigXYZCoordinates   return $ cieXYZ x y z -renderXYZCoordinates :: Tree-renderXYZCoordinates =-  withFillOpacity 0 $-  mkLinePath-  [ (x, 1-y)-  | (_nm, (x,y,_z)) <- Map.toList lightXYZCoordinates-  ]--lightLABCoordinates :: Map Nanometer (Double, Double, Double)-lightLABCoordinates = Map.map fn lightXYZCoordinates-  where-    fn (x,y,z) = cieLABView d65 (cieXYZ x y z)--renderLABCoordinates :: Tree-renderLABCoordinates =-  withFillOpacity 0 $-  mkLinePath-  [ (a/350, (1-b)/150)-  | (_nm, (_l,a,b)) <- Map.toList lightLABCoordinates-  ]- {-# NOINLINE coneSensitivity #-} -- (Long, Medium, Short)+-- | Cone sensitivity by light wavelength. coneSensitivity :: Map Nanometer (Double, Double, Double) coneSensitivity = unsafePerformIO $ do   dat <- BS.readFile =<< getDataFileName "data/cone_sensitivity_lms.csv"@@ -69,19 +63,3 @@     Left err -> error err     Right vec -> return $ Map.fromList       [ (nm, (l,m,fromMaybe 0 s)) | (nm,l,m,s) <- V.toList vec, nm <= 700 ]--renderSensitivity :: Tree-renderSensitivity = mkGroup-  [ withStrokeColor "blue"  $ draw (\(_,_,s) -> s)-  , withStrokeColor "green" $ draw (\(_,m,_) -> m)-  , withStrokeColor "red"   $ draw (\(l,_,_) -> l)-  ]-  where-    draw fn = withFillOpacity 0 $ mkLinePath-      [ (fromIntegral (ang-minAng) / maxAng * width, fn val * height)-      | (ang, val) <- Map.toList coneSensitivity-      ]-    width = 100-    height = -50-    maxAng = fromIntegral $ fst (Map.findMax coneSensitivity) - minAng-    minAng = fst (Map.findMin coneSensitivity)
src/Reanimate/Constants.hs view
@@ -1,3 +1,8 @@+{- |+  Reanimate configures a consistent, default canvas. The values of this default+  can be observed via the constants in this module. Keep in mind, these values+  describe the /default/ canvas and will not apply to custom viewports.+-} module Reanimate.Constants   ( screenWidth   , screenHeight@@ -11,9 +16,24 @@  import           Graphics.SvgTree -screenWidth, screenHeight, screenTop :: Fractional a => a-screenBottom, screenLeft, screenRight :: Fractional a => a+-- | Number of units from the left-most point to the right-most point on the screen.+screenWidth :: Fractional a => a +-- | Number of units from the bottom to the top of the screen.+screenHeight :: Fractional a => a++-- | Position of the top of the screen.+screenTop :: Fractional a => a++-- | Position of the bottom of the screen.+screenBottom :: Fractional a => a++-- | Position of the left side of the screen.+screenLeft :: Fractional a => a++-- | Position of the right side of the screen.+screenRight :: Fractional a => a+ screenWidth = 16 screenHeight = 9 screenTop = screenHeight/2@@ -21,8 +41,11 @@ screenLeft = -screenWidth/2 screenRight = screenWidth/2 +-- | SVG allows measurements in inches which have to be converted to local units.+--   This value describes how many local units there are in an inch. defaultDPI :: Dpi defaultDPI = 96 +-- | Default thickness of lines. defaultStrokeWidth :: Double defaultStrokeWidth = 0.05
src/Reanimate/Debug.hs view
@@ -1,3 +1,10 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Debug   ( traceSVG   , traceA@@ -20,16 +27,19 @@ traceBuffer = unsafePerformIO (newIORef [])  {-# NOINLINE traceSVG #-}+-- | Add SVG image to trace stack. traceSVG :: SVG -> a -> a traceSVG = traceA . staticFrame (recip 60)  {-# NOINLINE traceA #-}+-- | Add animation to trace stack. traceA :: Animation -> a -> a traceA a v = unsafePerformIO $ do   modifyIORef' traceBuffer (a :)   evaluate v  {-# NOINLINE playTraces #-}+-- | Evaluate argument and play back the trace stack. playTraces :: a -> Animation playTraces v = unsafePerformIO $ do   _   <- evaluate v
src/Reanimate/Driver.hs view
@@ -87,7 +87,7 @@ >   view                     Play animation in browser window. >   render                   Render animation to file. -Neither the 'check' nor the 'view' command take any additional arguments.+Neither the \'check\' nor the \'view\' command take any additional arguments. Rendering animation can be controlled with these arguments:  > Usage: PROG render [-o|--target FILE] [--fps FPS] [-w|--width PIXELS]@@ -109,7 +109,9 @@ reanimate animation = do   Options {..} <- getDriverOptions   case optsCommand of-    Raw  -> setFPS 60 >> renderSvgs animation+    Raw {..} -> do+      setFPS 60+      renderSvgs rawOutputFolder rawFrameOffset rawPrettyPrint animation     Test -> do       setNoExternals True       -- hSetBinaryMode stdout True
src/Reanimate/Driver/CLI.hs view
@@ -22,6 +22,10 @@  data Command   = Raw+    { rawOutputFolder :: FilePath+    , rawFrameOffset  :: Int+    , rawPrettyPrint  :: Bool+    }   | Test   | Check   | View@@ -99,14 +103,15 @@ options = Options <$> commandP  commandP :: Parser Command-commandP = subparser(command "raw" rawCommand-  <> command "test" testCommand+commandP = subparser(+     command "test" testCommand   <> commandGroup "Internal commands"   <> internal )   <|> hsubparser     ( command "check" checkCommand     <> command "view" viewCommand     <> command "render" renderCommand+    <> command "raw" rawCommand     )   <|> infoParser viewCommand @@ -114,7 +119,22 @@ rawCommand = info parse     (progDesc "Output raw SVGs for animation at 60 fps. Used internally by viewer.")   where-    parse = pure Raw+    parse = Raw+      <$> strOption+        ( long "output" <>+          short 'o' <>+          metavar "PATH" <>+          help "Output folder" <>+          value ".")+      <*> option auto+        ( long "offset" <>+          metavar "NUMBER" <>+          help "Frame offset" <>+          value 0)+      <*> switch+        ( long "pretty-print" <>+          short 'p' <>+          help "Pretty print svg")  testCommand :: ParserInfo Command testCommand = info (parse <**> helper)
src/Reanimate/Driver/Magick.hs view
@@ -6,12 +6,12 @@ import System.Directory (findExecutable)  -- |The name of the ImageMagick command. On Unix-like operating systems, the--- command 'convert' does not conflict with the name of other commands. On--- Windows, ImageMagick version 7 is readily available, the command 'magick'--- should be present, and is preferred over 'convert'. If it is not present,--- 'convert' is assumed to be the relevant command.+-- command \'convert\' does not conflict with the name of other commands. On+-- Windows, ImageMagick version 7 is readily available, the command \'magick\'+-- should be present, and is preferred over \'convert\'. If it is not present,+-- \'convert\' is assumed to be the relevant command. magickCmd :: String--- The use of 'unsafeperformIO' is justified on the basis that if 'magick' is+-- The use of 'unsafeperformIO' is justified on the basis that if \'magick\' is -- found once, it will always be present. magickCmd = unsafePerformIO $ do   mPath <- findExecutable "magick"
src/Reanimate/Driver/Server.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE ScopedTypeVariables #-} module Reanimate.Driver.Server   ( serve@@ -5,30 +6,30 @@   ) where  import           Control.Concurrent-import           Control.Exception       (SomeException, catch, finally)+import           Control.Exception      (SomeException, catch, finally) import           Control.Monad-import           Control.Monad.Fix       (fix)-import           Data.Text               (Text)-import qualified Data.Text               as T-import qualified Data.Text.IO            as T-import qualified Data.Text.Read          as T-import           GHC.Environment         (getFullArgs)+import           Data.IORef+import           Data.Text              (Text)+import qualified Data.Text              as T+import qualified Data.Text.Read         as T+import           Data.Time+import           GHC.Environment        (getFullArgs)+import           Language.Haskell.Ghcid import           Network.WebSockets import           Paths_reanimate-import           Reanimate.Misc          (runCmdLazy, runCmd_)-import           System.Directory        (createDirectoryIfMissing,-                                          doesFileExist, findFile,-                                          listDirectory, makeAbsolute,-                                          removeDirectoryRecursive,-                                          withCurrentDirectory)-import           System.Environment      (getProgName)+import           Reanimate.Misc         (runCmdLazy, runCmd_)+import           System.Directory       (createDirectoryIfMissing,+                                         doesFileExist, findFile, listDirectory,+                                         makeAbsolute,+                                         withCurrentDirectory)+import           System.Environment     (getProgName) import           System.Exit-import           System.Process import           System.FilePath import           System.FSNotify import           System.IO import           System.IO.Temp-import           Web.Browser             (openBrowser)+import           System.Process+import           Web.Browser            (openBrowser)  opts :: ConnectionOptions opts = defaultConnectionOptions@@ -39,49 +40,56 @@   hSetBuffering stdin NoBuffering   self <- maybe requireOwnSource pure mbSelfPath   when verbose $-    putStrLn $ "Found own source code at: " ++ self+    logMsg $ "Found own source code at: " ++ self   hasConnectionVar <- newMVar False +  ghci <- ghciBackend mbGHCPath self+   -- There might already browser window open. Wait 2s to see if that window   -- connects to us. If not, open a new window.   _ <- forkIO $ do     threadDelay (2*10^(6::Int))     hasConn <- readMVar hasConnectionVar     unless hasConn openViewer--  putStrLn "Listening..."+  logMsg "Listening..."   let options = ServerOptions         { serverHost = "127.0.0.1"         , serverPort = 9161         , serverConnectionOptions = opts         , serverRequirePong = Nothing }-  runServerWithOptions options $ \pending -> do-    putStrLn "New connection received."-    hasConn <- swapMVar hasConnectionVar True-    if hasConn-      then do-        putStrLn "Already connected to browser. Rejecting."-        rejectRequestWith pending defaultRejectRequest-      else withSystemTempDirectory "reanimate-svgs" $ \tmpDir -> do-        createDirectoryIfMissing True tmpDir-        conn <- acceptRequest pending-        slave <- newEmptyMVar-        let handler = modifyMVar_ slave $ \tid -> do-              putStrLn "Reloading code..."-              killThread tid-              forkIO $ ignoreErrors $ slaveHandler verbose mbGHCPath extraGHCOpts conn self tmpDir-            killSlave = do-              tid <- takeMVar slave-              killThread tid-        stop <- watchFile watch self handler-        putMVar slave =<< forkIO (return ())-        handler-        let loop = do-              -- FIXME: We don't use msg here.-              _msg <- receiveData conn :: IO T.Text-              handler-              loop-        loop `finally` (removeDirectoryRecursive tmpDir >> swapMVar hasConnectionVar False >> stop >> killSlave)+  withSystemTempDirectory "reanimate-svgs" $ \tmpDir ->+    runServerWithOptions options $ \pending -> do+      logMsg "New connection received."+      hasConn <- swapMVar hasConnectionVar True+      if hasConn+        then do+          logMsg "Already connected to browser. Rejecting."+          rejectRequestWith pending defaultRejectRequest+        else do+          createDirectoryIfMissing True tmpDir+          conn <- acceptRequest pending+          slave <- newEmptyMVar+          let handler = modifyMVar_ slave $ \tid -> do+                logMsg "Reloading code..."+                killThread tid+                forkIO $ ignoreErrors $ slaveHandler verbose mbGHCPath extraGHCOpts conn ghci self tmpDir+              killSlave = do+                tid <- takeMVar slave+                killThread tid+          stop <- watchFile watch self handler+          putMVar slave =<< forkIO (return ())+          handler+          let loop = do+                -- FIXME: We don't use msg here.+                _msg <- receiveData conn :: IO T.Text+                handler+                loop+              cleanup = do+                stop+                killSlave+                _ <- swapMVar hasConnectionVar False+                return ()+          loop `finally` cleanup  ignoreErrors :: IO () -> IO () ignoreErrors action = action `catch` \(_::SomeException) -> return ()@@ -89,76 +97,97 @@ openViewer :: IO () openViewer = do   url <- getDataFileName "viewer-elm/dist/index.html"-  putStrLn "Opening browser..."+  logMsg "Opening browser..."   bSucc <- openBrowser url   if bSucc-      then putStrLn "Browser opened."+      then logMsg "Browser opened."       else hPutStrLn stderr $ "Failed to open browser. Manually visit: " ++ url -slaveHandler :: Bool -> Maybe FilePath -> [String] -> Connection -> FilePath -> FilePath -> IO ()-slaveHandler verbose mbGHCPath extraGHCOpts conn self svgDir =+slaveHandler :: Bool -> Maybe FilePath -> [String] -> Connection -> GhciBackend+             -> FilePath -> FilePath -> IO ()+slaveHandler verbose mbGHCPath extraGHCOpts conn ghci self svgDir =   withCurrentDirectory (takeDirectory self) $   withSystemTempDirectory "reanimate" $ \tmpDir ->   withTempFile tmpDir "reanimate.exe" $ \tmpExecutable handle -> do-    -- cap <- getNumCapabilities-    let n = 25-    sem <- newQSemN n+    outputFolder <- createTempDirectory svgDir "svgs"+    let frameFileName frameIdx =+          outputFolder </> show frameIdx <.> "svg"++    sentFrameCount <- newMVar False     hClose handle     lock <- newMVar ()-    sendTextData conn (T.pack "status\nCompiling")+    sendWebMessage conn $ WebStatus "Compiling"+    ghciThread <- forkIO $ do+      firstFrame <- newIORef True+      ghciReload ghci+      logMsg "GHCi reload done."+      ghciGenerate ghci outputFolder $ \frameIdx -> do+        first <- readIORef firstFrame+        writeIORef firstFrame False+        if first+          then do+            modifyMVar_ sentFrameCount $ \sent -> do+              unless sent $+                sendWebMessage conn $ WebFrameCount frameIdx+              logMsg "Framecount sent."+              return True+          else+            withMVar lock $ \_ ->+              sendWebMessage conn $ WebFrame frameIdx (frameFileName frameIdx)+      logMsg "GHCi render done."     ret <- case mbGHCPath of       Nothing -> do         let args = ["ghc", "--"] ++ ghcOptions tmpDir ++ extraGHCOpts ++ [takeFileName self, "-o", tmpExecutable]         when verbose $-          putStrLn $ "Running: " ++ showCommandForUser "stack" args+          logMsg $ "Running: " ++ showCommandForUser "stack" args         runCmd_ "stack" args       Just ghc -> do         let args = ghcOptions tmpDir ++ extraGHCOpts ++ [takeFileName self, "-o", tmpExecutable]         when verbose $-          putStrLn $ "Running: " ++ showCommandForUser ghc args+          logMsg $ "Running: " ++ showCommandForUser ghc args         runCmd_ ghc args+    logMsg "Compile done."     case ret of       Left err ->-        sendTextData conn $ T.pack $ "error\n" ++ unlines (lines err)-      Right{} -> runCmdLazy tmpExecutable execOpts $ \getFrame -> do-        (frameCount,_) <- expectFrame sem =<< getFrame-        sendTextData conn (T.pack $ "frame_count\n" ++ show frameCount)-        fix $ \loop -> do-          (frameIdx, frame) <- expectFrame sem =<< getFrame-          -- putStrLn $ "Got frame: " ++ show frameIdx-          let fileName = svgDir </> takeBaseName tmpExecutable <.> show frameIdx <.> "svg"-              -- pngName = replaceExtension fileName "png"-          _ <- forkIO $ do-            waitQSemN sem 1-            T.writeFile fileName frame-            -- runCmd "rsvg-convert"-            --   [ fileName-            --   , "--width=256" -- "--width=1024"-            --   , "--height=144" -- "--height=576"-            --   , "--output", pngName ]-            withMVar lock $ \_ ->-              sendTextData conn (T.pack $ "frame\n" ++ show frameIdx ++ "\n" ++ fileName)-            signalQSemN sem 1-          loop+        sendWebMessage conn $ WebError $ unlines (lines err)+      Right{} -> runCmdLazy tmpExecutable (execOpts outputFolder) $ \getFrame -> do+        frameCount <- expectFrame =<< getFrame+        modifyMVar_ sentFrameCount $ \sent -> do+          unless sent $+            sendWebMessage conn $ WebFrameCount frameCount+          return True+        replicateM_ frameCount $ do+          frameIdx <- expectFrame =<< getFrame+          withMVar lock $ \_ ->+            sendWebMessage conn $ WebFrame frameIdx (frameFileName frameIdx)+        logMsg "Optimized render done."+        killThread ghciThread   where-    execOpts = ["raw", "+RTS", "-N", "-M2G", "-RTS"]-    expectFrame :: QSemN -> Either String Text -> IO (Integer, Text)-    expectFrame sem (Left "") = do-      waitQSemN sem 25 -- =<< getNumCapabilities-      sendTextData conn (T.pack "status\nDone")+    execOpts output =+      [ "raw", "--output", output, "--offset", "1"+      , "+RTS", "-N", "-M2G", "-RTS"]+    expectFrame :: Either String Text -> IO Int+    expectFrame (Left "") = do+      sendWebMessage conn $ WebStatus "Done"       exitSuccess-    expectFrame _ (Left err) = do-      sendTextData conn $ T.pack $ "error\n" ++ err+    expectFrame (Left err) = do+      sendWebMessage conn $ WebError err       exitWith (ExitFailure 1)-    expectFrame _ (Right frame) =+    expectFrame (Right frame) =       case T.decimal frame of         Left err -> do           hPutStrLn stderr (T.unpack frame)           hPutStrLn stderr $ "expectFrame: " ++ err-          sendTextData conn $ T.pack $ "error\n" ++ err+          sendWebMessage conn $ WebError err           exitWith (ExitFailure 1)-        Right (frameNumber, rest) ->-          pure (frameNumber, rest)+        Right (frameNumber, "") ->+          pure frameNumber+        Right {} -> do+          let err = "Unexpected output"+          hPutStrLn stderr (T.unpack frame)+          hPutStrLn stderr $ "expectFrame: " ++ err+          sendWebMessage conn $ WebError err+          exitWith (ExitFailure 1)  watchFile :: WatchManager -> FilePath -> IO () -> IO StopListening watchFile watch file action = watchTree watch (takeDirectory file) check (const action)@@ -205,3 +234,65 @@  isHaskellFile :: FilePath -> Bool isHaskellFile path = takeExtension path `elem` [".hs", ".lhs"]++logMsg :: String -> IO ()+logMsg msg = do+    now <- getCurrentTime+    putStrLn $ formatTime defaultTimeLocale fmt now ++ ": " ++ msg+  where+    fmt = "%F %T%2Q"++-------------------------------------------------------------------------------+-- Ghci interface++-- stack+-- cabal+-- raw+-- none?+data GhciBackend = GhciBackend (MVar Ghci)++ghciBackend :: Maybe FilePath -> FilePath -> IO GhciBackend+ghciBackend mbGHCPath self = do+  let ghciProc =+        case mbGHCPath of+          Just ghcPath ->+            proc ghcPath $ ["--interactive", "+RTS"] ++ words memoryLimit ++ ["-RTS"]+          Nothing ->+            proc "stack" ["exec", "ghci", "--rts-options="++memoryLimit]+  (ghci, _loads) <- startGhciProcess ghciProc $ \_stream _msg -> return ()+  void $ exec ghci $ ":load " ++ self+  ref <- newMVar ghci+  return $ GhciBackend ref++ghciReload :: GhciBackend -> IO ()+ghciReload (GhciBackend ref) =+  withMVar ref $ \ghci ->+    void $ reload ghci++ghciGenerate :: GhciBackend -> FilePath -> (Int -> IO ()) -> IO ()+ghciGenerate (GhciBackend ref) target cb = withMVar ref $ \ghci -> do+  execStream ghci (":main raw --output=" ++ target ++ " --offset=1")+    $ \_ msg ->+      case reads msg of+        [(frameIdx,"")] -> cb frameIdx+        _               -> return ()++memoryLimit :: String+memoryLimit = "-M1G"++-------------------------------------------------------------------------------+-- Websocket API++data WebMessage+  = WebStatus String+  | WebError String+  | WebFrameCount Int+  | WebFrame Int FilePath++sendWebMessage :: Connection -> WebMessage -> IO ()+sendWebMessage conn msg = sendTextData conn $+  case msg of+    WebStatus txt   -> T.pack "status\n" <> T.pack txt+    WebError txt    -> T.pack "error\n" <> T.pack txt+    WebFrameCount n -> T.pack $ "frame_count\n" ++ show n+    WebFrame n path -> T.pack $ "frame\n" ++ show n ++ "\n" ++ path
src/Reanimate/Ease.hs view
@@ -1,3 +1,7 @@+{-|+  Easing functions modify the rate of change in animations.+  More examples can be seen here: <https://easings.net/>.+-} module Reanimate.Ease   ( Signal   , constantS@@ -7,7 +11,6 @@   , powerS   , bellS   , oscillateS-  , fromListS   , cubicBezierS   ) where @@ -15,15 +18,6 @@ --   composition. type Signal = Double -> Double -fromListS :: [(Double, Signal)] -> Signal-fromListS fns t = worker 0 fns-  where-    worker _ [] = 0-    worker now [(len, fn)] = fn (min 1 ((t-now) / min (1-now) len))-    worker now ((len, fn):rest)-      | now+len < t = worker (now+len) rest-      | otherwise = fn ((t-now) / len)- -- | Constant signal. -- --   Example:@@ -67,6 +61,13 @@     then 0.5 * (2*s)**steepness     else 1-0.5 * (2 - 2*s)**steepness +-- | Power curve signal. Takes a steepness parameter. 2 is a good default.+--+--   Example:+--+--   > signalA (powerS 2) drawProgress+--+--   <<docs/gifs/doc_powerS.gif>> powerS :: Double -> Signal powerS steepness s = s**steepness @@ -94,7 +95,7 @@ bellS steepness = curveS steepness . oscillateS  -- | Cubic Bezier signal. Gives you a fair amount of control over how the---   signal will 'curve'.+--   signal will curve. -- --   Example: --
src/Reanimate/Effect.hs view
@@ -79,7 +79,8 @@  -- | Modify the animation by applying the effect. If desired, you can apply multiple effects to single animation by calling this function multiple times. applyE :: Effect -> Animation -> Animation-applyE fn (Animation d genFrame) = Animation d $ \t -> fn d (d*t) $ genFrame t+applyE fn ani = let d = duration ani+                in mkAnimation d $ \t -> fn d (d*t) $ frameAt (d*t) ani  -- | Build an effect from an image-modifying function. This effect does not change as time passes. constE :: (Tree -> Tree) -> Effect
src/Reanimate/GeoProjection.hs view
@@ -1,6 +1,19 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE MagicHash    #-} {-# LANGUAGE MultiWayIf   #-}+{-|+Module      : Reanimate.GeoProjection+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++This module provides functions for mapping the surface of+a sphere on to a 2D plane. It also has convenience functions+for loading GeoJSON data.++-} module Reanimate.GeoProjection   ( Projection(..)   , XYCoord(..)@@ -108,143 +121,8 @@     !xPx = round $ ((lam+pi)/tau) * wMax     !yPx = round $ (1-((phi+halfPi)/pi)) * hMax -{- HLINT ignore -}--- findValidCoord :: Int -> Int -> Double -> Double -> (XYCoord -> LonLat) -> XYCoord -> XYCoord--- findValidCoord !w !h !wMax !hMax !p_inv (XYCoord x y) = foldr const (XYCoord x y)---     [ XYCoord x' y'---     | let !xi = round $ x * wMax---           !yi = round $ y * hMax---     , !ax <- [xi, xi-1, xi+1]---     , ax >= 0---     , ax < w---     , let !x' = fromIntegral ax / wMax---     , !ay <- [yi, yi-1, yi+1]---     , ay >= 0---     , ay < h---     , let !y' = fromIntegral ay / hMax---     , validLonLat $! p_inv $! XYCoord x' y'---     ]---- isInWorld :: Projection -> XYCoord -> Bool--- isInWorld p coord =---     odd $ length $ isInWorld' p coord------ isInWorld' :: Projection -> XYCoord -> [(Double, Double)]--- isInWorld' p (XYCoord x y) =---     [ (x1, y1)---     | (XYCoord x1 y1, XYCoord x2 y2) <- world---     , (y1 > y) /= (y2 > y) -- y is between y1 and y2---     , x < (x2 - x1) * (y - y1) / (y2 - y1) + x1 -- x is to the left of the line---     ]---   where---     world = worldPolygon p------ worldPolygon :: Projection -> [(XYCoord, XYCoord)]--- worldPolygon p =---     interp (-pi, -halfPi) (-pi, halfPi) ++---     interp (-pi, halfPi) (pi, halfPi) ++---     interp (pi, halfPi) (pi, -halfPi) ++---     interp (pi, -halfPi) (-pi, -halfPi)---   where---     apply (lam, phi) = projectionForward p $ LonLat lam phi---     steps = 100---     interp (x1, y1) (x2,y2) =---       [ ( apply (fromToS x1 x2 (n/steps), fromToS y1 y2 (n/steps))---         , apply (fromToS x1 x2 ((n+1)/steps), fromToS y1 y2 ((n+1)/steps)))---       | n <- [0..steps-1]]---- findNearestPixel :: MutableImage s PixelRGBA8 -> Int -> Int -> Int -> Int -> ST s PixelRGBA8--- findNearestPixel src w h srcX srcY = worker $ take 20---     [ (x, y)---     | n <- [1..]---     , x <- [srcX-n .. srcY+n]---     , y <- if x == srcX-n || x == srcX+n then [srcY-n,srcY+n] else [srcY-n .. srcY+n]---     , x >= 0---     , y >= 0---     , x < w---     , y < h---     ]---   where---     worker [] = pure $ PixelRGBA8 0xFF 0x00 0x00 0xFF---     worker ((x,y):rest) = do---       this <- readPixel src x y---       if this == blank---         then worker rest---         else return this---     blank = PixelRGBA8 0x00 0x00 0x00 0x00----- Original version:        134,925 pixels/second--- Inlined projections:     136,332 pixels/second--- TEST: no write pixels:   134,288 pixels/second !!!--- Fast theta:            1,489,719 pixels/second--- Cached theta:          3,622,254 pixels/second---- to equirectangularP:   9,015,326 pixels/second---                        9,680,217---                        9,466,744---                       14,830,330--- to lambertP:           6,735,973--- to mercatorP:          4,248,927--- to mollweideP:         3,593,545--- to hammerP:            3,237,699--- to bottomleyP:         3,864,848--- to sinusoidalP:        7,020,756--- to wernerP:            4,433,295--- to bonneP:             4,071,187--- to augustP:            2,553,177--- to collignonP:         5,486,849--- to eckert1P:           7,428,849--- to eckert3P:           6,666,936--- to eckert5P:           6,106,492--- to faheyP:             5,137,291--- to foucautP:           3,983,151--- to lagrangeP:          3,850,611--- interpFastP :: Image PixelRGBA8 -> Projection -> Projection -> Double -> Image PixelRGBA8--- interpFastP !src (Projection _ p1 p1_inv) (Projection _ p2 p2_inv) !t = runST $ do---     unsafeIOToST $ putStrLn "Allocating new array"---     !img <- newMutableImage w h---     unsafeIOToST $ putStrLn "done"---     start <- unsafeIOToST $ getCurrentTime---     let factor = 2---         total = w*factor * h*factor---     let l1 =---           loopTo (w*factor) $ \x -> do---             loopTo (h*factor) $ \y -> do---               let thisIndex = (x*h*factor+y)---               when (thisIndex `mod` 1000000 == 0) $ unsafeIOToST $ do---                 now <- getCurrentTime---                 let diff = realToFrac (diffUTCTime now start):: Double---                 printf "%.2f pixels/second\n" (fromIntegral (total-thisIndex) / diff)---               let !x1' = fromIntegral x / (wMax*fromIntegral factor)---                   !y1' = fromIntegral y / (hMax*fromIntegral factor)---                   !lonlat = p1_inv $! XYCoord x1' y1'---                   -- p = srcPixel src lonlat---               -- unsafeIOToST (evaluate lonlat)------               when (validLonLat lonlat) $ do---                 p <- srcPixelFast src wMax hMax lonlat---                 when (pixelOpacity p /= 0) $ do---                   let XYCoord !x1 !y1 = p1 lonlat---                       -- !coord = p2 lonlat---                       XYCoord !x2 !y2 = p2 lonlat -- findValidCoord w h wMax hMax p2_inv $ p2 lonlat---                       !x3 = round $ fromToS x1 x2 t * wMax---                       !y3 = round $ (1 - fromToS y1 y2 t) * hMax :: Int---                   -- unsafeIOToST (evaluate coord)---                   -- return ()---                   when (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h) $---                     writePixel img x3 y3 p---     l1---     unsafeFreezeImage img---   where---     loopTo m fn = go m---       where go 0 = return ()---             go n = fn (n-1) >> go (n-1)---     !w = imageWidth src---     !h = imageHeight src---     !wMax = fromIntegral (w-1)---     !hMax = fromIntegral (h-1)-+-- | Interpolate between two projections and apply the result to an image in+--   equirectangular format. The source image must have an aspect ratio of 2:1. interpP :: Image PixelRGBA8 -> Projection -> Projection -> Double -> Image PixelRGBA8 interpP src p1 _ 0 = project src p1 interpP src _ p2 1 = project src p2@@ -252,49 +130,32 @@     !img <- newMutableImage w h      let factor = 2-        -- total = w*factor * h*factor     let l1 = do-          -- start <- unsafeIOToST $ getCurrentTime           loopTo (w*factor) $ \x -> do             loopTo (h*factor) $ \y -> do-              -- let thisIndex = (x*h*factor+y)-              -- when (thisIndex `mod` 1000000 == 0) $ unsafeIOToST $ do-              --   now <- getCurrentTime-              --   let diff = realToFrac (diffUTCTime now start):: Double-              --   printf "%.2f pixels/second: %s\n" (fromIntegral (total-thisIndex) / diff) label1               let !x1' = fromIntegral x / (wMax*fromIntegral factor)                   !y1' = fromIntegral y / (hMax*fromIntegral factor)                   !lonlat = p1_inv $! XYCoord x1' y1'-                  -- p = srcPixel src lonlat                when (validLonLat lonlat) $ do                 p <- srcPixelFast src wMax hMax lonlat                 when (pixelOpacity p /= 0) $ do                   let XYCoord x1 y1 = p1 lonlat-                      -- XYCoord x2 y2 = findValidCoord w h wMax hMax p2_inv $ p2 lonlat                       XYCoord x2 y2 = p2 lonlat                       !x3 = round $ fromToS x1 x2 t * wMax                       !y3 = round $ (1 - fromToS y1 y2 t) * hMax                   when (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h) $                     writePixel img x3 y3 p         l2 = do-          -- start <- unsafeIOToST $ getCurrentTime           loopTo (w*factor) $ \x ->             loopTo (h*factor) $ \y -> do-              -- let thisIndex = (x*h*factor+y)-              -- when (thisIndex `mod` 1000000 == 0) $ unsafeIOToST $ do-              --   now <- getCurrentTime-              --   let diff = realToFrac (diffUTCTime now start):: Double-              --   printf "%.2f pixels/second: %s\n" (fromIntegral (total-thisIndex) / diff) label2               let !x2' = fromIntegral x / (wMax*fromIntegral factor)                   !y2' = fromIntegral y / (hMax*fromIntegral factor)                   !lonlat = p2_inv (XYCoord x2' y2')-                  -- p = srcPixel src lonlat               when (validLonLat lonlat) $ do                 p <- srcPixelFast src wMax hMax lonlat                 when (pixelOpacity p /= 0) $ do                   let XYCoord x2 y2 = p2 lonlat-                      -- XYCoord x1 y1 = findValidCoord w h wMax hMax p1_inv $ p1 lonlat                       XYCoord x1 y1 = p1 lonlat                       !x3 = round $ fromToS x1 x2 t * wMax                       !y3 = round $ (1 - fromToS y1 y2 t) * hMax@@ -313,6 +174,11 @@     !wMax = fromIntegral (w-1)     !hMax = fromIntegral (h-1) +-- | Interpolate between two projections and apply the result to an image in+--   equirectangular format. The source image must have an aspect ratio of 2:1.+--   Only the areas inside of the two bounding boxes (applying to the source and+--   target projection, respectively) are mapped. Pixels outside of these bounding-boxes+--   are undefined. interpBBP :: Image PixelRGBA8 -> Projection -> Projection ->             (Double,Double,Double,Double) -> (Double,Double,Double,Double) -> Double -> Image PixelRGBA8 interpBBP !src (Projection _ p1 p1_inv) !(Projection _ p2 p2_inv) (fx,fy,fw,fh) (tx, ty, tw, th) !t = runST $ do@@ -325,18 +191,12 @@                   !y1' = fromIntegral y / (hMax*fromIntegral factor)               when (x1' >= fx && x1' <= fx+fw && y1' >= fy && y1' <= fy+fh) $ do                 let !lonlat = p1_inv $! XYCoord x1' y1'-                      -- p = srcPixel src lonlat                  when (validLonLat lonlat) $ do-                    -- let LonLat lam phi = lonlat-                    --     !xPx = ((lam+pi)/tau)-                    --     !yPx = (((phi+halfPi)/pi))-                    -- when (xPx >= fx && xPx <= fx+fw && yPx >= fy && yPx <= fy+fh) $ do                     p <- srcPixelFast src wMax hMax lonlat                     when (pixelOpacity p /= 0) $ do                       let XYCoord x1 y1 = p1 lonlat                           XYCoord x2 y2 = p2 lonlat-                          -- XYCoord x2 y2 = findValidCoord w h wMax hMax p2_inv $ p2 lonlat                           !x3 = round $ fromToS x1 x2 t * wMax                           !y3 = round $ (1 - fromToS y1 y2 t) * hMax                       when (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h) $@@ -348,12 +208,10 @@                   !y2' = fromIntegral y / (hMax*fromIntegral factor)               when (x2' >= tx && x2' <= tx+tw && y2' >= ty && y2' <= ty+th) $ do                 let !lonlat = p2_inv (XYCoord x2' y2')-                    -- p = srcPixel src lonlat                 when (validLonLat lonlat) $ do                   p <- srcPixelFast src wMax hMax lonlat                   when (pixelOpacity p /= 0) $ do                     let XYCoord x2 y2 = p2 lonlat-                        -- XYCoord x1 y1 = findValidCoord w h wMax hMax p1_inv $ p1 lonlat                         XYCoord x1 y1 = p1 lonlat                         !x3 = round $ fromToS x1 x2 t * wMax                         !y3 = round $ (1 - fromToS y1 y2 t) * hMax@@ -363,15 +221,6 @@       then l1 >> l2       else l2 >> l1 -    -- when False $-    --   forM_ [0..w-1] $ \x ->-    --     forM_ [0..h-1] $ \y -> do-    --       let x1 = fromIntegral x / (wMax)-    --           y1 = 1 - fromIntegral y / (hMax)-    --       this <- readPixel img x y-    --       when (isBlank this) $-    --         when (isInWorld (mergeP p1 p2 t) (XYCoord x1 y1)) $-    --           writePixel img x y =<< findNearestPixel img w h x y     unsafeFreezeImage img   where     loopTo m fn = go m@@ -394,21 +243,32 @@ eqDouble :: Double -> Double -> Bool eqDouble a b = abs (a-b) < epsilon +-- | XY coordinates on a 2D plane. Valid ranges go from 0 to 1, inclusive. data XYCoord = XYCoord !Double !Double -- 0 to 1   deriving (Read,Show,Eq,Ord)++-- | Longitude and latitude. Valid range for longitude is -pi to +pi.+--   Valid range for latitude is -pi/2 to +pi/2. data LonLat = LonLat !Double !Double -- -pi to +pi, -halfPi to +halfPi   deriving (Read,Show,Eq,Ord) instance Hashable LonLat where   hashWithSalt s (LonLat a b) = hashWithSalt s (a,b)++-- | Projections are named bi-directional mappings between a sphere+--   and a 2D plane. data Projection = Projection-  { projectionLabel   :: String+  { projectionLabel   :: String -- ^ Name of the projection.   , projectionForward :: !(LonLat -> XYCoord)-    ---    -- (Double# -> Double# -> (# Double#, Double# #))+    -- ^ Mapping from longitude and latitude on a sphere to+    --   XY coordinates on a 2D plane.   , projectionInverse :: !(XYCoord -> LonLat)+    -- ^ Mapping from XY coordinates on a 2D plane to longitude+    --   and latitude on a sphere.   }  -- FIXME: Verify that 'src' has an aspect ratio of 2:1.+-- | Apply on an image in equirectangular format. The source image+--   therfore must have an aspect ratio of 2:1. project :: Image PixelRGBA8 -> Projection -> Image PixelRGBA8 project src (Projection _label _ pInv) = generateImage fn w h   where@@ -430,6 +290,7 @@ _validXYCoord :: XYCoord -> Bool _validXYCoord (XYCoord x y) = x >= 0 && x <= 1 && y >= 0 && y <= 1 +-- | Returns @True@ iff a projection is consistent and complete. isValidP :: Projection -> Bool isValidP (Projection _label p pInv) = and     [ check x y@@ -446,6 +307,7 @@       in not (validLonLat lonlat) || eqLonLat lonlat lonlat2           || trace (show (lonlat, lonlat2)) False +-- | Translate the lower-most point of a projection by an offset. moveBottomP :: Double -> Projection -> Projection moveBottomP offset (Projection label p pInv) = Projection label p' pInv'   where@@ -454,9 +316,11 @@         XYCoord x y -> XYCoord x (fromToS offset 1 y)     pInv' (XYCoord x y) = pInv (XYCoord x ((y-offset)/(1-offset))) +-- | Translate the top-most point of a projection by an offset. moveTopP :: Double -> Projection -> Projection moveTopP offset = flipYAxisP . moveBottomP offset . flipYAxisP +-- | Invert the Y axis of projection. flipYAxisP :: Projection -> Projection flipYAxisP (Projection label p pInv) = Projection label p' pInv'   where@@ -467,6 +331,7 @@       let LonLat lam phi = pInv (XYCoord x (1-y))       in LonLat lam (negate phi) +-- | Scale X and Y axis of projection. scaleP :: Double -> Double -> Projection -> Projection scaleP xScale yScale (Projection label p pInv) = Projection label forward inverse   where@@ -476,7 +341,8 @@     inverse (XYCoord x y) =       pInv $ XYCoord ((x-0.5)/xScale+0.5) ((y-0.5)/yScale+0.5) -+-- | Attempt to smoothly interpolate two projections. The result may not be continuous+--   and 'interpP' may give prettier results. mergeP :: Projection -> Projection -> Double -> Projection mergeP p1 p2 t = Projection (projectionLabel p1 ++ "/" ++ projectionLabel p2) p pInv   where@@ -587,6 +453,7 @@         lam = 2 * atan2 (z*x) (2*(2*z**2-1))         phi = asin (z*y) +-- | <<docs/gifs/doc_cylindricalEqualAreaP.gif>> cylindricalEqualAreaP :: Double -> Projection cylindricalEqualAreaP phi0 = Projection "lambert" forward inverse   where@@ -723,6 +590,7 @@           | v < lower = v+upper-lower           | otherwise = v +-- | <<docs/gifs/doc_cassiniP.gif>> cassiniP :: Projection cassiniP = Projection "cassini" forward inverse   where@@ -735,7 +603,7 @@         lam = atan2 (tan x) (cos y)         phi = asin (sin y * cos x) -+-- | <<docs/gifs/doc_augustP.gif>> augustP :: Projection augustP = scaleP 0.70 0.70 $ Projection "august"  forward inverse   where@@ -875,6 +743,7 @@         phi = 2 * atan2 y (1 + faheyK)  {-# INLINE foucautP #-}+-- | <<docs/gifs/doc_foucautP.gif>> foucautP :: Projection foucautP = Projection "foucaut" forward inverse   where@@ -898,6 +767,7 @@         lam = x * sqrtPi / 2 / (cos phi * cosk * cosk)  {-# INLINE lagrangeP #-}+-- | <<docs/gifs/doc_lagrangeP.gif>> lagrangeP :: Projection lagrangeP = Projection "lagrange" forward inverse   where@@ -944,7 +814,7 @@   -+-- | Map for all features and render the geometry. drawFeatureCollection :: GeoFeatureCollection a -> (a -> SVG -> SVG) -> SVG drawFeatureCollection geo fn = mkGroup   [ fn (feature ^. properties) $ renderGeometry (feature ^. geometry)@@ -952,6 +822,7 @@   ]  {-# INLINE loadFeatureCollection #-}+-- | Load GeoJSON from a filepath and render the geometry. loadFeatureCollection :: FromJSON a => FilePath -> (a -> SVG -> SVG) -> SVG loadFeatureCollection path = unsafePerformIO $ do   mbGeo <- decodeFileStrict path@@ -965,6 +836,7 @@ -- pointsToRadians :: SVG -> SVG -- applyProjection :: Projection -> SVG -> SVG +-- | Render GeoJSON geometry as SVG. renderGeometry :: GeospatialGeometry -> SVG renderGeometry shape =   case shape of@@ -988,9 +860,15 @@     _ -> None  +-- | Apply a projection to an SVG image. This is a lossy transformation+--   but the default tolerance is low enough that inaccuracies should not+--   be visible. applyProjection :: Projection -> SVG -> SVG applyProjection = applyProjection' 1e-2 +-- | Apply a projection to an SVG image with a specified tolerance.+--   Projections may turn straight lines into disjointed curves and+--   the tolerance argument determined the accuracy of this transformation. applyProjection' :: Double -> Projection -> SVG -> SVG applyProjection' tolerance p = mapSvgLines start   where
src/Reanimate/LaTeX.hs view
@@ -1,5 +1,12 @@ {-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE ScopedTypeVariables #-}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.LaTeX   ( latex   , latexWithHeaders@@ -40,6 +47,7 @@ latex :: T.Text -> Tree latex = latexWithHeaders [] +-- | Invoke latex with extra script headers. latexWithHeaders :: [T.Text] -> T.Text -> Tree latexWithHeaders = someTexWithHeaders "latex" "dvi" [] @@ -51,6 +59,7 @@  where   script = mkTexScript exec args headers tex +-- | Invoke latex and separate results. latexChunks :: [T.Text] -> [Tree] latexChunks chunks | pNoExternals = map mkText chunks latexChunks chunks                = worker (svgGlyphs $ latex $ T.concat chunks) chunks@@ -67,6 +76,7 @@ xelatex :: Text -> Tree xelatex = xelatexWithHeaders [] +-- | Invoke xelatex with extra script headers. xelatexWithHeaders :: [T.Text] -> T.Text -> Tree xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"] @@ -82,6 +92,7 @@ ctex :: T.Text -> Tree ctex = ctexWithHeaders [] +-- | Invoke xelatex with extra script headers + ctex headers. ctexWithHeaders :: [T.Text] -> T.Text -> Tree ctexWithHeaders headers = xelatexWithHeaders ("\\usepackage[UTF8]{ctex}" : headers) 
src/Reanimate/Math/Balloon.hs view
@@ -1,6 +1,16 @@ {-# LANGUAGE MultiWayIf        #-} {-# LANGUAGE OverloadedStrings #-}-module Reanimate.Math.Balloon where+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-}+module Reanimate.Math.Balloon+  ( balloon+  , balloon'+  ) where  import           Control.Lens import qualified Data.Vector                as V@@ -15,9 +25,20 @@  -- import           Debug.Trace +-- | Inflate SVG shapes like a balloon. This works by hiding corners+--   that are more than @t@ percent distant from the starting point+--   relative to the maximum diameter of the shape.+--+--   Example:+--+--   > animate $ balloon (scale 8 $ center $ latex "X")+--+--   <<docs/gifs/doc_balloon.gif>> balloon :: SVG -> (Double -> SVG) balloon = balloon' 0.01 +-- | Same as @balloon'@ but with a given tolerance for converting+--   SVG shapes to polygons. balloon' :: Double -> SVG -> (Double -> SVG) balloon' tol svg = \t ->     mkGroup
src/Reanimate/Math/Common.hs view
@@ -1,3 +1,16 @@+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}+{-|+Module      : Reanimate.Math.Common+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Low-level primitives related to computational geometry.++-} module Reanimate.Math.Common   ( -- * Ring     Ring(..)@@ -11,14 +24,13 @@     -- * Math   , area                -- :: Fractional a => V2 a -> V2 a -> V2 a -> a   , area2X              -- :: Fractional a => V2 a -> V2 a -> V2 a -> a-  , epsilon             -- :: Fractional a => a-  , epsEq               -- :: (Ord a, Fractional a) => a -> a -> Bool-  , isLeftTurn          -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool-  , isLeftTurnOrLinear  -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool-  , isRightTurn         -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool-  , isRightTurnOrLinear -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool-  , direction           -- :: Fractional a => V2 a -> V2 a -> V2 a -> a+  , isLeftTurn          -- :: (Num a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+  , isLeftTurnOrLinear  -- :: (Num a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+  , isRightTurn         -- :: (Num a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+  , isRightTurnOrLinear -- :: (Num a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+  , direction           -- :: Num a => V2 a -> V2 a -> V2 a -> a   , isInside            -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> V2 a -> Bool+  , isInsideStrict      -- :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> V2 a -> Bool   , barycentricCoords   -- :: Fractional a => V2 a -> V2 a -> V2 a -> V2 a -> (a, a, a)   , rayIntersect        -- :: (Fractional a, Ord a) => (V2 a,V2 a) -> (V2 a,V2 a) -> Maybe (V2 a)   , isBetween           -- :: (Ord a, Fractional a) => V2 a -> (V2 a, V2 a) -> Bool@@ -27,6 +39,7 @@   , approxDist          -- :: (Real a, Fractional a) => V2 a -> V2 a -> a   , distance'           -- :: (Real a, Fractional a) => V2 a -> V2 a -> Double   , triangleAngles      -- :: V2 Double -> V2 Double -> V2 Double -> (Double, Double, Double)+  , Epsilon(..)   ) where  import           Data.Vector    (Vector)@@ -36,84 +49,111 @@ import           Linear.V2 import           Linear.V3 import           Linear.Vector+import           Linear.Epsilon +instance Epsilon Rational where+  nearZero r = r==0++-- | Circular collection of pairs. newtype Ring a = Ring (Vector (V2 a)) +-- | Number of elements in the ring. ringSize :: Ring a -> Int ringSize (Ring v) = length v +-- | Safe method for accessing elements in the ring. ringAccess :: Ring a -> Int -> V2 a ringAccess (Ring v) i = v V.! mod i (length v) +-- | Clamp index to within the usable range for the ring. ringClamp :: Ring a -> Int -> Int ringClamp (Ring v) i = mod i (length v) +-- | Convert ring to a vector. ringUnpack :: Ring a -> Vector (V2 a) ringUnpack (Ring v) = v +-- | Convert vector to a ring. ringPack :: Vector (V2 a) -> Ring a ringPack = Ring +-- | Map each element of a ring. ringMap :: (V2 a -> V2 b) -> Ring a -> Ring b ringMap fn (Ring v) = Ring (V.map fn v) +-- | Compute the intersection of two pairs of nodes in the ring. ringRayIntersect :: Ring Rational -> (Int, Int) -> (Int,Int) -> Maybe (V2 Rational) ringRayIntersect p (a,b) (c,d) =   rayIntersect (ringAccess p a, ringAccess p b) (ringAccess p c, ringAccess p d) -+-- | Compute area of triangle. area :: Fractional a => V2 a -> V2 a -> V2 a -> a area a b c = 1/2 * area2X a b c +-- | Compute 2x area of triangle. This avoids a division. area2X :: Fractional a => V2 a -> V2 a -> V2 a -> a area2X (V2 a1 a2) (V2 b1 b2) (V2 c1 c2) =   det33 (V3 (V3 a1 a2 1)             (V3 b1 b2 1)             (V3 c1 c2 1)) -epsilon :: Fractional a => a-epsilon = 1e-13--epsEq :: (Ord a, Fractional a) => a -> a -> Bool-epsEq a b = abs (a-b) < epsilon+compareEpsZero :: (Ord a, Fractional a, Epsilon a) => a -> Ordering+compareEpsZero val+  | nearZero val  = EQ+  | otherwise     = compare val 0  {-# INLINE isLeftTurn #-}--- Left turn.-isLeftTurn :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+-- | Return @True@ iff the line from @p1@ to @p2@ makes a left-turn to @p3@.+isLeftTurn :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool isLeftTurn p1 p2 p3 =-  case compare (direction p1 p2 p3) 0 of+  case compareEpsZero (direction p1 p2 p3) of     LT -> True-    EQ -> False -- colnear+    EQ -> False -- colinear     GT -> False  {-# INLINE isLeftTurnOrLinear #-}-isLeftTurnOrLinear :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+-- | Return @True@ iff the line from @p1@ to @p2@ does not make a right-turn to @p3@.+isLeftTurnOrLinear :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool isLeftTurnOrLinear p1 p2 p3 =-  case compare (direction p1 p2 p3) 0 of+  case compareEpsZero (direction p1 p2 p3) of     LT -> True-    EQ -> True -- colnear+    EQ -> True -- colinear     GT -> False  {-# INLINE isRightTurn #-}-isRightTurn :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+-- | Return @True@ iff the line from @p1@ to @p2@ makes a right-turn to @p3@.+isRightTurn :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool isRightTurn a b c = not (isLeftTurnOrLinear a b c)  {-# INLINE isRightTurnOrLinear #-}-isRightTurnOrLinear :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> Bool+-- | Return @True@ iff the line from @p1@ to @p2@ does not make a left-turn to @p3@.+isRightTurnOrLinear :: (Fractional a, Ord a, Epsilon a) => V2 a -> V2 a -> V2 a -> Bool isRightTurnOrLinear a b c = not (isLeftTurn a b c)  {-# INLINE direction #-}-direction :: Fractional a => V2 a -> V2 a -> V2 a -> a+-- | Compute the change in direction in a line between the three points.+direction :: Num a => V2 a -> V2 a -> V2 a -> a direction p1 p2 p3 = crossZ (p3-p1) (p2-p1)  {-# INLINE isInside #-}+-- | Returns @True@ if the fourth argument is inside the triangle or+--   on the border. isInside :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> V2 a -> Bool isInside a b c d =-    s >= 0 && s <= 1 && t >= 0 && t <= 1+    s >= 0 && s <= 1 && t >= 0 && t <= 1 && i >= 0 && i <= 1   where-    (s, t, _) = barycentricCoords a b c d+    (s, t, i) = barycentricCoords a b c d +{-# INLINE isInsideStrict #-}+-- | Returns @True@ iff the fourth argument is inside the triangle.+isInsideStrict :: (Fractional a, Ord a) => V2 a -> V2 a -> V2 a -> V2 a -> Bool+isInsideStrict a b c d =+    s > 0 && s < 1 && t > 0 && t < 1 && i > 0 && i < 1+  where+    (s, t, i) = barycentricCoords a b c d+ {-# INLINE barycentricCoords #-}+-- | Compute relative coordinates inside the triangle. Invariant: @a+b+c=1@ barycentricCoords :: Fractional a => V2 a -> V2 a -> V2 a -> V2 a -> (a, a, a) barycentricCoords (V2 x1 y1) (V2 x2 y2) (V2 x3 y3) (V2 x y) =     (lam1, lam2, lam3)@@ -126,6 +166,7 @@   {-# INLINE rayIntersect #-}+-- | Compute intersection of two infinite lines. rayIntersect :: (Fractional a, Ord a) => (V2 a,V2 a) -> (V2 a,V2 a) -> Maybe (V2 a) rayIntersect (V2 x1 y1,V2 x2 y2) (V2 x3 y3, V2 x4 y4)   | yBot == 0 = Nothing@@ -138,12 +179,14 @@     yBot = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)  {-# INLINE isBetween #-}+-- | Returns @True@ iff a point is on a line segment. isBetween :: (Ord a, Fractional a) => V2 a -> (V2 a, V2 a) -> Bool isBetween (V2 x y) (V2 x1 y1, V2 x2 y2) =   ((y1 > y) /= (y2 > y) || y == y1 || y == y2) && -- y is between y1 and y2   ((x1 > x) /= (x2 > x) || x == x1 || x == x2)  {-# INLINE lineIntersect #-}+-- | Compute intersection of two line segments. lineIntersect :: (Ord a, Fractional a) => (V2 a, V2 a) -> (V2 a, V2 a) -> Maybe (V2 a) lineIntersect a b =   case rayIntersect a b of@@ -153,16 +196,20 @@  -- circleIntersect :: (Ord a, Fractional a) => (V2 a, V2 a) -> (V2 a, V2 a) -> [V2 a] -distSquared :: (Fractional a) => V2 a -> V2 a -> a+-- | Compute the square of the distance between two points.+distSquared :: (Num a) => V2 a -> V2 a -> a distSquared a b = quadrance (a ^-^ b) +-- | Approximate the distance between two points. approxDist :: (Real a, Fractional a) => V2 a -> V2 a -> a approxDist a b = realToFrac (sqrt (realToFrac (distSquared a b) :: Double)) +-- | Approximate the distance between two points. distance' :: (Real a, Fractional a) => V2 a -> V2 a -> Double distance' a b = sqrt (realToFrac (distSquared a b))  -- sum of angles is always pi.+-- | Approximate the angles of a triangle. triangleAngles :: V2 Double -> V2 Double -> V2 Double -> (Double, Double, Double) triangleAngles a b c =     (findAngle (b-a) (c-a)
− src/Reanimate/Math/Compatible.hs
@@ -1,325 +0,0 @@-{-# LANGUAGE MultiWayIf #-}-module Reanimate.Math.Compatible where--import           Data.List-import           Data.Maybe-import           Data.Ord-import qualified Data.Vector                   as V-import           Linear.V2-import           Linear.Vector-import           Reanimate.Math.Common-import           Reanimate.Math.Polygon-import           Reanimate.Debug-import           Reanimate.Math.Render-import           Reanimate.Svg-import           Reanimate.Animation---- import           Debug.Trace--truncateP :: V2 Rational -> V2 Rational-truncateP = fmap (realToFrac . (realToFrac :: Rational -> Double))--mkSteinerPoints :: V2 Rational -> V2 Rational -> Int -> [V2 Rational]-mkSteinerPoints a b s_ = [ lerp (i / (s + 1)) b a | i <- [1 .. s] ]-  where s = fromIntegral s_---- 0..i,j..n-1--- i..j-split1Link :: Polygon -> Int -> Int -> Int -> (Polygon, Polygon)-split1Link p i j s | j < i = split1Link p j i s-split1Link p i j s =-  (mkPolygon $ V.fromList left, mkPolygon $ V.fromList right)- where-  n     = pSize p-  sp    = mkSteinerPoints (pAccess p i) (pAccess p j) s-  left  = map (pAccess p) [0 .. i] ++ sp ++ map (pAccess p) [j .. n - 1]-  right = map (pAccess p) [i .. j] ++ reverse sp--steiner2Link :: Polygon -> Int -> Int -> V2 Rational-steiner2Link p i j | j < i = steiner2Link p j i-steiner2Link p i j-  | isNeighbour-  = error "steiner2Link: Points are neighbours"-  | isParent-  = error "steiner2Link: Points can directly see each other."-  | not (isStraightLine || isGrandparent || oneBendBetween p i j)-  = error-    $  "steiner2Link: Cannot construct 2-link chain between points: "-    ++ show (i, j, pParent p i j, pParent p i (pParent p i j))-  | otherwise-  = truncateP $ lerp 0.5 (fst vect) (intersects !! 0)- where-  distToV       = approxDist (fst vect)-  isNeighbour   = i == pNext p j || i == pPrev p j-  isParent      = pParent p i j == i-  isGrandparent = pParent p i (pParent p i j) == i-  isStraightLine =-    direction (pAccess p j) (pAccess p $ pParent p i j) (pAccess p i) == 0-  intersects =-    sortOn distToV-      $ snd vect-      : [ u-        | n <- [0 .. pSize p - 1]-        , let edge = (pAccess p n, pAccess p $ pNext p n)-        , u <- case rayIntersect vect edge of-          Nothing -> []-          Just u  -> [u]-        , isBetween u edge-        , u /= fst vect-        , isForward vect u-        ]-  iP = pAdjustOffset p i-  jP = pAdjustOffset p j-  vect-    | isStraightLine-    = let p1 = lerp 0.5 (pAccess p i) (pAccess p j)-          p2 = case p1 - pAccess p i of-            V2 x y -> p1 + V2 (-y) x -- rotate 90 degrees.-      in  (p1, p2)-    | otherwise-    = fromMaybe-        (error $ "No window overlap: " ++ show-          (isStraightLine, isGrandparent, oneBendBetween p i j)-        )-      $ listToMaybe-      $ [ (p1, p1 + (p2 - p1) + (p3 - p1))-        | (a, b)       <- ssspWindows iP-        , (c, d)       <- ssspWindows jP-        , (p1, p2, p3) <- if-          | a == c    -> pure (a, b, d)-          | a == d    -> pure (a, b, c)-          | b == c    -> pure (b, a, d)-          | b == d    -> pure (b, a, c)-          | otherwise -> []-        ]-  isForward (a, b) v = not (isBetween a (b, v))---- 0..i,s,j..n-1--- i..j,s-split2Link :: Polygon -> Int -> Int -> (Polygon, Polygon)-split2Link p i j | j < i = split2Link p j i-split2Link p i j = (mkPolygon $ V.fromList left, mkPolygon $ V.fromList right)- where-  s     = steiner2Link p i j-  n     = pSize p-  left  = map (pAccess p) [0 .. i] ++ [s] ++ map (pAccess p) [j .. n - 1]-  right = map (pAccess p) [i .. j] ++ [s]--data Link = OneLink | TwoLink--splitNLink :: Polygon -> Int -> [(Link, Int)] -> (Polygon, Polygon)-splitNLink p i js = (mkPolygon $ V.fromList left, mkPolygon $ V.fromList right)- where-  n        = pSize p-  left = map (pAccess p) [0 .. i] ++ steiners ++ map (pAccess p) [j .. n - 1]-  right    = map (pAccess p) [i .. j] ++ reverse steiners-  j        = snd (last js)-  steiners = splitNLinks p i js--- 0, [(TwoLink,2),(OneLink,3)]--- 0, [(OneLink,5),(TwoLink,3)]-splitNLinks :: Polygon -> Int -> [(Link, Int)] -> [V2 Rational]-splitNLinks _p _i []              = []-splitNLinks p  i  [(TwoLink, j )] = [steiner2Link p i j]-splitNLinks _p _i [(OneLink, _j)] = []-splitNLinks p i ((TwoLink, j) : (OneLink, j') : xs) =-  let-    (l, r) = split2Link p i j-    p'     = selectContains l r (pAccess p j')-    s      = steiner2Link p i j-    sIdx =-      fromMaybe (error "missing steiner") $ V.elemIndex s (polygonPoints p')-  in-    s : splitNLinks p' sIdx ((TwoLink, j') : xs)-splitNLinks p i ((TwoLink, j) : (TwoLink, j') : xs) =-  let-    (l, r) = split2Link p i j-    p'     = selectContains l r (pAccess p j')-    s      = steiner2Link p i j-    sIdx =-      fromMaybe (error "missing steiner") $ V.elemIndex s (polygonPoints p')-  in-    s : splitNLinks p' sIdx ((OneLink, j) : (TwoLink, j') : xs)-splitNLinks p i ((OneLink, j) : (TwoLink, j') : xs) =-  let-    (l, r) = split2Link p j j'-    p'     = selectContains l r (pAccess p i)-    p''    = selectContains l r (pAccess p j')-    s      = steiner2Link p j j'-    s'     = steiner2Link p' i sIdx-    sIdx   = fromMaybe (error "missing steiner sIdx")-      $ V.elemIndex s (polygonPoints p')-    sIdx' = fromMaybe (error "missing steiner sIdx'")-      $ V.elemIndex s' (polygonPoints p'')-  in-    s' : s : splitNLinks p'' sIdx' ((OneLink, j') : xs)-splitNLinks _p _i _ = error "splitNLinks: invalid input"--selectContains :: Polygon -> Polygon -> V2 Rational -> Polygon-selectContains p1 p2 elt | V.elem elt (polygonPoints p1) = p1-                         | V.elem elt (polygonPoints p2) = p2-                         | otherwise = error "elt not member of either polygons"--- [(P, Vis)] -> Polygon -> [P]-{--If there is a 1-link path, connect it.-  Recurse into the two new polygon pairs.--If there is a 2-link path, connect it, possibly add bogus steiner point.-  Recurse into the two new polygon pairs.--If there is a n-link path, connect it, add bogus steiner points.-  Recurse into the two new polygon pairs.--}---- a 2 b 1 c--- ab = 2-link of (a,b)--- abc = 2-link of (cut-a-ab-b, c)--- steps: a ab abc c--- a, [(2,b), (1,c)]--- [(ab, p1,p2)]--{--, mkGroup-  [ withStrokeColor "green" $mkGroup-    [ mkLine (x1,y1) (x2,y2)-    , mkLine (x1',y1') (x2',y2') ]-  | (a,b) <- mWins-  , (i,j) <- oWins-  , a == i || a == j || b == i || b == j-  , not (sort [a,b] == sort [i,j])-  , let V2 x1 y1 = realToFrac <$> a-        V2 x2 y2 = realToFrac <$> b-        V2 x1' y1' = realToFrac <$> i-        V2 x2' y2' = realToFrac <$> j-  ]--}----type Points = V.Vector (V2 Rational)-type Edges = [(Int, Int, Int)]-data Mesh = Mesh { meshPoints :: Points, meshEdges :: Edges }-data MeshPair = MeshPair Points Points Edges--- The points in a RelMesh are:---   relMeshStatic ++ x where Ax = B--- data RelMesh = RelMesh---   { relMeshStatic :: Points---   , relMeshEdges  :: Edges---   , relMeshA      :: Matrix Double---   , relMeshB      :: Matrix Double---   }--- data RelMeshPair = RelMeshPair Points Edges (Matrix Double) (Matrix Double) (Matrix Double) (Matrix Double)---------- Use SSSP to find 1-link connections--- Compute visibility windows for each node--- Check windows for ovelap, giving 2-link connections.--- Use floyd to generate all other connections--- (a,b,1)--- (a,b,2)--- floyd-compatiblyTriangulateP :: Polygon -> Polygon -> [(Polygon, Polygon)]-compatiblyTriangulateP a b-  | pSize a /= pSize b = error "polygon size mismatch"-  | otherwise = traceSVG (showStep a b) $ compatiblyTriangulateP'-    (pSetOffset a 0)-    (pSetOffset a 0)-    (pSetOffset b 0)--showStep :: Polygon -> Polygon -> SVG-showStep a b = mkGroup-  [ translate (-3) 0-    $ mkGroup [withFillColor "grey" $ polygonShape a, polygonNumDots a]-  , translate 3    0-    $ mkGroup [withFillColor "grey" $ polygonShape b, polygonNumDots b]-  ]--compatiblyTriangulateP' :: Polygon -> Polygon -> Polygon -> [(Polygon, Polygon)]-compatiblyTriangulateP' aOrigin a b-  | n == 3 = traceSVG (showStep a b) $ {- trace ("Done") $ -} [(a, b)]-  | otherwise =-    -- trace ("aOneLink: " ++ show aOneLink) $-    -- trace ("bOneLink: " ++ show bOneLink) $-    -- trace ("aTwoLink: " ++ show aTwoLink) $-    -- trace ("bTwoLink: " ++ show bTwoLink) $-    traceSVG (showStep a b) $ case bestOneLink of-    Nothing -> case bestTwoLink of-      Nothing -> error $ "no 2-links"-      Just (nodeL, nodeR) ->-        {-trace-            (show-              ("two link" :: String, toOriginIndex nodeL, toOriginIndex nodeR)-            )-          $ -}-            let (aL, aR) = if (nodeL, nodeR) `elem` aOneLink-                  then split1Link a nodeL nodeR 1-                  else split2Link a nodeL nodeR-                (bL, bR) = if (nodeL, nodeR) `elem` bOneLink-                  then split1Link b nodeL nodeR 1-                  else split2Link b nodeL nodeR-            in  compatiblyTriangulateP' aOrigin aL bL-                  ++ compatiblyTriangulateP' aOrigin aR bR-    Just (nodeL, nodeR) ->-      {-trace-          (show ("one link" :: String, toOriginIndex nodeL, toOriginIndex nodeR)-          )-        $ -}-          let (aL, aR) = split1Link a nodeL nodeR 0-              (bL, bR) = split1Link b nodeL nodeR 0-          in  compatiblyTriangulateP' aOrigin aL bL-                ++ compatiblyTriangulateP' aOrigin aR bR- where-  -- toOriginIndex idx =-  --   ( idx-  --   , fromMaybe (-1) (V.elemIndex (pAccess a idx) (polygonPoints aOrigin))-  --     + polygonOffset aOrigin-  --   )-  n = pSize a-  bestOneLink =-    listToMaybe (sortOn (Down . nodeDist) (aOneLink `intersect` bOneLink))-  bestTwoLink = listToMaybe-    (sortOn (Down . nodeDist)-            ((aOneLink ++ aTwoLink) `intersect` (bOneLink ++ bTwoLink))-    )-  aOneLink = polygonOneLinks a-  bOneLink = polygonOneLinks b-  aTwoLink = polygonTwoLinks a-  bTwoLink = polygonTwoLinks b-  nodeDist (i, j) = min (j - i) (n - j + i)--oneBendBetween :: Polygon -> Int -> Int -> Bool-oneBendBetween _p _a _b = False--- oneBendBetween p a b =---     direction---       (pAccess p b)---       (pAccess p (pParent p a b))---       (pAccess p $ obstructedBy b) == 0 &&---     (pParent p a b) /= obstructedBy b---   where---     obstructedBy n =---       case pParent p a n of---         i -> if i == a then n else obstructedBy i--polygonTwoLinks :: Polygon -> [(Int, Int)]-polygonTwoLinks p =-  [ (i, j)-  | i <- [0 .. n - 1]-  , j <- [i + 2 .. n - 1]-  , not (i == 0 && j == n - 1)-  , pParent p i j /= i -- check for 1-link-  , let isTwoLink      = pParent p i (pParent p i j) == i-        isStraightLine = oneBendBetween p i j--- Points on a straight line should be 2-link even though they are not--- direct grandparents.-  , isTwoLink || isStraightLine-  ]-  where n = pSize p--polygonOneLinks :: Polygon -> [(Int, Int)]-polygonOneLinks p =-  [ (i, j)-  | i <- [0 .. pSize p - 1]-  , j <- [i + 2 .. pSize p - 1]-  , not (i == 0 && j == pSize p - 1)-  , pParent p i j == i-  ]
− src/Reanimate/Math/DCEL.hs
@@ -1,891 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}--- Code originally from: https://github.com/wereHamster/haskell-half-edge-mesh-module Reanimate.Math.DCEL where--import           Codec.Picture.Types-import           Control.Lens-import           Control.Monad.State-import           Control.Monad.Writer-import           Data.List-import qualified Data.List              as L-import           Data.Map               (Map)-import qualified Data.Map               as M-import           Data.Maybe-import qualified Data.Set               as S-import qualified Data.Text              as T-import qualified Data.Vector            as V--- import           Debug.Trace-import           Linear.Metric-import           Linear.V2-import           Linear.Vector (lerp, (^/))-import           Reanimate-import           Reanimate.Math.Common  (isInside, lineIntersect, triangleAngles)-import           Reanimate.Math.Polygon-import qualified Reanimate.Morph.Rigid  as Rigid-import           Text.Printf--type VertexId = Int-type EdgeId = Int-type FaceId = Int---- Invariants:---   No duplicate positions---   All edges have an opposite twin---   Edge prev/next keep the same direction (CW or CCW)---   twin (twin edge) = edge---   All next/prev links have the same face-data Mesh a = Mesh-    { _meshIdCounter :: Int-    , _meshOuterFace :: FaceId-    , _meshVertices  :: Map VertexId (Vertex a)-    , _meshEdges     :: Map EdgeId Edge-    , _meshFaces     :: Map FaceId Face-    } deriving (Show)--data Vertex a = Vertex-    { _vertexId       :: VertexId-    , _vertexPosition :: a-    -- , _vertexEdge     :: EdgeId -- Index to CCW edge pointing to this vertex.-    } deriving (Show)--data Edge = Edge-    { _edgeId     :: EdgeId-    , _edgeVertex :: VertexId-    , _edgeTwin   :: EdgeId-    , _edgeNext   :: EdgeId-    , _edgePrev   :: EdgeId-    , _edgeFace   :: FaceId-    } deriving (Show)--data Face = Face-    { _faceId   :: FaceId-    , _faceEdge :: EdgeId-    } deriving (Show)--type MeshM position a = State (Mesh position) a--validMesh :: Mesh a -> [String]-validMesh Mesh{..} = execWriter $ do-  return ()--makeLenses ''Mesh-makeLenses ''Vertex-makeLenses ''Edge-makeLenses ''Face--meshGetEdge :: EdgeId -> Mesh a -> Edge-meshGetEdge eid Mesh{..} = M.findWithDefault err eid _meshEdges-  where-    err = error $ "Edge not found: " ++ show eid--meshGetVertex :: VertexId -> Mesh a -> Vertex a-meshGetVertex vid Mesh{..} = M.findWithDefault err vid _meshVertices-  where-    err = error $ "Vertex not found: " ++ show vid--meshGetFace :: FaceId -> Mesh a -> Face-meshGetFace fid Mesh{..} = M.findWithDefault err fid _meshFaces-  where-    err = error $ "Face not found: " ++ show fid--meshAngles :: Mesh (V2 Double) -> [Double]-meshAngles mesh@Mesh{..} =-  [ faceMinAngle fid mesh-  | fid <- M.keys _meshFaces-  ]--meshEdgeAngle :: Mesh (V2 Double) -> EdgeId -> Double-meshEdgeAngle m eId =-    ang-  where-    edge = meshGetEdge eId m-    next = meshGetEdge (edge ^. edgeNext) m-    prev = meshGetEdge (edge ^. edgePrev) m-    v1 = meshGetVertex (prev ^. edgeVertex) m ^. vertexPosition-    v2 = meshGetVertex (edge ^. edgeVertex) m ^. vertexPosition-    v3 = meshGetVertex (next ^. edgeVertex) m ^. vertexPosition-    (_, ang, _) = triangleAngles v1 v2 v3--facePositions :: FaceId -> Mesh a -> [a]-facePositions fid m =-  [ meshGetVertex (edge^.edgeVertex) m ^. vertexPosition-  | eid <- faceEdges fid m-  , let edge = meshGetEdge eid m-  ]--faceEdges :: FaceId -> Mesh a -> [EdgeId]-faceEdges fid m = worker (meshGetEdge lastEdge m ^.edgeNext)-  where-    face = meshGetFace fid m-    lastEdge = face^.faceEdge-    worker eid-      | eid == lastEdge = [eid]-      | otherwise = eid : worker (meshGetEdge eid m ^. edgeNext)--faceMinAngle :: FaceId -> Mesh (V2 Double) -> Double-faceMinAngle fid m = minimum (map (meshEdgeAngle m) (faceEdges fid m))--ppMesh :: Show a => Mesh a -> String-ppMesh Mesh{..} = unlines-  [ printf "Outer face: %d" _meshOuterFace-  , ""-  , "Vertices:"-  , printf "%4s %5s %10s" ("ID"::String) ("Edge"::String) ("Position"::String)-  , unlines-    [ printf "%4d %10s" _vertexId  (show _vertexPosition)-    | Vertex{..} <- M.elems _meshVertices-    ]-  , "Edges:"-  , printf "%4s %3s %4s %4s %4s %4s"-    ("ID"::String) ("Vtx"::String) ("Twin"::String) ("Next"::String)-    ("Prev"::String) ("Face"::String)-  , unlines-    [ printf "%4d %3d %4d %4d %4d %4d" _edgeId _edgeVertex _edgeTwin _edgeNext _edgePrev _edgeFace-    | Edge{..} <- M.elems _meshEdges-    ]-  , "Faces:"-  , unlines-    [ printf "%4d %4d" _faceId _faceEdge-    | Face{..} <- M.elems _meshFaces-    ]-  ]--emptyMesh :: Mesh a-emptyMesh = Mesh 1 0 M.empty M.empty M.empty---- addFace :: FaceId -> [a] -> MeshM a ()--- addFace parentFace subPoly = do---   innerFace <- createFace---   vIds <- mapM getVertex pts---   edges     <- forM (zip vIds (tail vIds ++ take 1 vIds)) $ \(v0, v1) -> do---     e <- do---       mbEdge <- findEdge' v0 v1---       case mbEdge of---         Just e  -> pure (e ^. edgeId)---         Nothing -> do---           e <- createEdge v1---           twin <- createEdge v0---           setTwinEdge e twin---           setFace twin parentFace---           return e---     setFace e innerFace---     modifyVertex (vertexEdge .~ e) v0---     pure e----   setFaceEdge innerFace (head edges)---   forM_ (zip edges (tail edges ++ take 1 edges)) $ \(e0, e1) ->---     linkEdges e0 e1--polygonsMesh :: Eq a => [a] -> [[a]] -> MeshM a ()-polygonsMesh outer trigs = do-  polygonMeshOuter (reverse outer)-  modify $ meshFaces .~ M.empty-  forM_ trigs $ \pts -> do-    innerFace <- createFace-    vIds <- mapM getVertex pts-    edges     <- forM (zip vIds (tail vIds ++ take 1 vIds)) $ \(_v0, v1) -> do-      e <- createEdge v1-      setFace e innerFace-      -- modifyVertex (vertexEdge .~ e) v0-      pure e--    setFaceEdge innerFace (head edges)-    forM_ (zip edges (tail edges ++ take 1 edges)) $ \(e0, e1) ->-      linkEdges e0 e1-    forM edges $ \e -> do-      edge <- getEdge e-      prev <- getEdge (edge ^. edgePrev)-      mbTwin <- findEdge' (edge ^. edgeVertex) (prev ^. edgeVertex)-      case mbTwin of-        Nothing -> return ()-        Just twin ->-          setTwinEdge e (twin^.edgeId)---- Positions must be in CW order-polygonMesh :: [a] -> MeshM a ()-polygonMesh vs = do-  outerFace <- gets _meshOuterFace-  innerFace <- createFace-  vIds      <- mapM createVertex vs-  edges     <- forM (zip vIds (tail vIds ++ take 1 vIds)) $ \(v0, v1) -> do-    e  <- createEdge v1 -- Edge from v0 to v1-    e' <- createEdge v0 -- Twin edge from v1 to v0-    setFace e  innerFace-    setFace e' outerFace-    -- modifyVertex (vertexEdge .~ e) v0-    setTwinEdge e e'-    pure e--  outerEdge <- _edgeTwin <$> getEdge (head edges)-  setFaceEdge innerFace (head edges)-  setFaceEdge outerFace outerEdge--  forM_ (zip edges (tail edges ++ take 1 edges)) $ \(e0, e1) -> do-    linkEdges e0 e1-    e0' <- _edgeTwin <$> getEdge e0-    e1' <- _edgeTwin <$> getEdge e1-    linkEdges e1' e0'--polygonMeshOuter :: [a] -> MeshM a ()-polygonMeshOuter vs = do-  outerFace <- gets _meshOuterFace-  vIds      <- mapM createVertex vs-  edges     <- forM vIds $ \v0 -> do-    e' <- createEdge v0-    setFace e' outerFace-    pure e'--  setFaceEdge outerFace (head edges)--  let f = Face outerFace (head edges)-  modify $ meshFaces %~ M.insert outerFace f--  forM_ (zip edges (tail edges ++ take 1 edges)) $ \(e0, e1) -> do-    linkEdges e0 e1---newId :: MeshM a Int-newId = do-  counter <- gets _meshIdCounter-  modify $ meshIdCounter %~ succ-  return counter--createVertex :: a -> State (Mesh a) VertexId-createVertex position = do-  k <- newId-  let v = Vertex k position -- (error "_vertexEdge not set")-  modify $ meshVertices %~ M.insert k v-  return k--getVertex :: Eq a => a -> State (Mesh a) VertexId-getVertex position = do-  vs <- gets _meshVertices-  case L.find comparingPosition (M.assocs vs) of-    Nothing     -> createVertex position-    Just (k, _) -> return k-  where comparingPosition (_k, v) = (_vertexPosition v) == position--requireVertex :: VertexId -> MeshM a (Vertex a)-requireVertex vid = do-  ret <- gets (M.lookup vid . _meshVertices)-  case ret of-    Nothing -> error "Invalid vertex id"-    Just v  -> pure v--modifyVertex :: (Vertex a -> Vertex a) -> VertexId -> State (Mesh a) ()-modifyVertex f k = modify $ meshVertices %~ M.adjust f k--getEdge :: EdgeId -> State (Mesh a) Edge-getEdge e = do-  edges <- gets _meshEdges-  maybe (bug edges) return $ M.lookup e edges-    --return $ fromJust $ M.lookup e edges-  where bug _edges = error $ "Can't find edge with id " ++ (show e)--withEdge :: EdgeId -> (Edge -> State (Mesh a) ()) -> State (Mesh a) ()-withEdge e f = f =<< getEdge e---- findEdge :: VertexId -> VertexId -> State (Mesh a) EdgeId--- findEdge v0 v1 = do---   edges <- gets _meshEdges---   case L.find comparingVertices (M.assocs edges) of---     Nothing     -> error $ "No edge between: " ++ show (v0, v1)---     Just (k, _) -> return k---   where comparingVertices (k, e) = (k, _edgeNext e) == (v0, v1)--createEdge :: VertexId -> State (Mesh a) EdgeId-createEdge v = do-  k <- newId-  let e = Edge k-               v-               (error $ "_edgeTwin not set " ++ show (k,v))-               (error $ "_edgeNext not set " ++ show (k,v))-               (error $ "_edgePrev not set " ++ show (k,v))-               (error $ "_edgeFace not set " ++ show (k,v))-  modify $ meshEdges %~ M.insert k e-  return k--modifyEdge :: (Edge -> Edge) -> EdgeId -> State (Mesh a) ()-modifyEdge f k = modify $ meshEdges %~ M.adjust f k--linkEdges :: EdgeId -> EdgeId -> State (Mesh a) ()-linkEdges e0 e1 = do-  setNextEdge e0 e1-  setPreviousEdge e1 e0--setNextEdge :: EdgeId -> EdgeId -> State (Mesh a) ()-setNextEdge e0 e1 = modifyEdge (edgeNext .~ e1) e0--setPreviousEdge :: EdgeId -> EdgeId -> State (Mesh a) ()-setPreviousEdge e0 e1 = modifyEdge (edgePrev .~ e1) e0--setTwinEdge :: EdgeId -> EdgeId -> MeshM a ()-setTwinEdge e0 e1 = do-  modifyEdge (edgeTwin .~ e1) e0-  modifyEdge (edgeTwin .~ e0) e1--setFace :: EdgeId -> FaceId -> State (Mesh a) ()-setFace e0 f0 = modifyEdge (edgeFace .~ f0) e0--updateFaces :: EdgeId -> FaceId -> State (Mesh a) ()-updateFaces e0 f0 = do-  e <- getEdge e0-  setFace e0 f0-  worker (e ^. edgeNext)- where-  worker e1-    | e0 == e1 = return ()-    | otherwise = do-      e <- getEdge e1-      setFace e1 f0-      worker (e ^. edgeNext)--createFace :: State (Mesh a) FaceId-createFace = do-  k <- newId-  let f = Face k (error "_faceEdge not set")-  modify $ meshFaces %~ M.insert k f-  return k--modifyFace :: (Face -> Face) -> FaceId -> State (Mesh a) ()-modifyFace f k = modify $ meshFaces %~ M.adjust f k--setFaceEdge :: FaceId -> EdgeId -> MeshM a ()-setFaceEdge f0 e0 = modifyFace (\f -> f { _faceEdge = e0 }) f0--deleteFace :: FaceId -> MeshM a ()-deleteFace f0 = modify $ meshFaces %~ M.delete f0--getFace :: FaceId -> State (Mesh a) Face-getFace f = do-  faces <- gets _meshFaces-  maybe bug return (M.lookup f faces)-  where bug = error $ "Can't find face with id " ++ (show f)---buildMesh :: State (Mesh a) b -> Mesh a-buildMesh f = execState f emptyMesh--numVertices :: Mesh a -> Int-numVertices = M.size . _meshVertices--numEdges :: Mesh a -> Int-numEdges = M.size . _meshEdges--numFaces :: Mesh a -> Int-numFaces = M.size . _meshFaces--vertices :: Mesh a -> [Vertex a]-vertices = M.elems . _meshVertices--outgoingEdges :: VertexId -> Mesh a -> [Edge]-outgoingEdges v mesh =-  [ meshGetEdge (edge ^. edgeTwin) mesh-  | edge <- M.elems (_meshEdges mesh)-  , _edgeVertex edge == v-  ]----- for each inner node:---    get all neighbour nodes. sort by CCW---    compute laplacian---    compute angle-based---    check correctness---    move vertex----   p    e--- _ -> u -> v---   p'   e'--- _ <- u <- v------   p    e1   e--- _ -> u -> t -> v---   p'   e1'  e'--- _ <- u <- t <- v------ faces stay the same-splitEdge :: VertexId -> EdgeId -> MeshM a EdgeId-splitEdge t e = do-  eEdge     <- getEdge e-  eEdgeTwin <- getEdge (eEdge ^. edgeTwin)--  e1        <- createEdge t-  setFace e1 (eEdge ^. edgeFace)-  -- modifyVertex (vertexEdge .~ e1) t--  e1' <- createEdge (eEdgeTwin ^. edgeVertex)-  setFace e1' (eEdgeTwin ^. edgeFace)-  setTwinEdge e1 e1'--  let oldPrev = eEdge ^. edgePrev-      oldNext = eEdgeTwin ^. edgeNext--  linkEdges oldPrev             e1-  linkEdges (eEdge ^. edgeTwin) e1'-  linkEdges e1                  e-  linkEdges e1'                 oldNext--  modifyEdge (edgeVertex .~ t) (eEdge ^. edgeTwin)--  return e1--splitTriangle :: V2 Double -> EdgeId -> MeshM (V2 Double) (FaceId, FaceId, FaceId, FaceId)-splitTriangle vertex e = do-  -- outer <- gets _meshOuterFace-  edge <- getEdge e-  next <- getEdge (edge^.edgeNext)-  twin <- getEdge (edge^.edgeTwin)-  next' <- getEdge (twin^.edgeNext)--  t         <- createVertex vertex-  _ <- splitEdge t e--  smoothVertex t--  (f1,f2) <- insertEdge (edge^.edgeFace) t (next^.edgeVertex)-  (f3,f4) <- insertEdge (twin^.edgeFace) t (next'^.edgeVertex)-  return (f1,f2,f3,f4)--splitOuterTriangle :: V2 Double -> EdgeId -> MeshM (V2 Double) (FaceId, FaceId)-splitOuterTriangle vertex e = do-  -- outer <- gets _meshOuterFace-  edge <- getEdge e-  twin <- getEdge (edge^.edgeTwin)-  next' <- getEdge (twin^.edgeNext)--  t         <- createVertex vertex-  _ <- splitEdge t e--  (f1,f2) <- insertEdge (twin^.edgeFace) t (next'^.edgeVertex)-  return (f1,f2)-  -- return (outer, outer)--findEdge :: FaceId -> VertexId -> MeshM a Edge-findEdge f v = do-  edges <- gets (M.elems . _meshEdges)-  case-      listToMaybe [ e | e <- edges, e ^. edgeFace == f, e ^. edgeVertex == v ]-    of-      Nothing -> error $ "Edge not found: " ++ show (f, v)-      Just e  -> pure e--findEdge' :: VertexId -> VertexId -> MeshM a (Maybe Edge)-findEdge' v0 v1 = do-  m <- get-  pure $ listToMaybe-    [ e-    | e <- M.elems (m^.meshEdges)-    , e ^. edgeVertex == v1-    , let prev = meshGetEdge (e ^. edgePrev) m-    , prev ^. edgeVertex == v0-    ]--findEdge'' :: [FaceId] -> VertexId -> MeshM a (Maybe Edge)-findEdge'' fs v = do-  edges <- gets (M.elems . _meshEdges)-  pure $-      listToMaybe [ e | e <- edges, e ^. edgeFace `notElem` fs, e ^. edgeVertex == v ]--{----}-deleteEdge :: EdgeId -> MeshM a ()-deleteEdge e0 = do-  e <- getEdge e0-  e' <- getEdge (e^.edgeTwin)-  linkEdges (e^.edgePrev) (e'^.edgeNext)-  linkEdges (e'^.edgePrev) (e^.edgeNext)-  updateFaces (e^.edgeNext) (e^.edgeFace)-  setFaceEdge (e^.edgeFace) (e^.edgeNext)-  deleteFace (e'^.edgeFace)-  modify $ meshEdges %~ M.delete (e^.edgeId)-  modify $ meshEdges %~ M.delete (e'^.edgeId)--insertEdge :: FaceId -> VertexId -> VertexId -> MeshM a (FaceId, FaceId)-insertEdge f0 v0 v1 = do-  hv0 <- findEdge f0 v0-  hv1 <- findEdge f0 v1-  f1  <- createFace-  f2  <- createFace-  h1  <- createEdge v1-  h2  <- createEdge v0-  setFaceEdge f1 h1-  setFaceEdge f2 h2-  setTwinEdge h1 h2-  linkEdges (hv0 ^. edgeId) h1-  linkEdges (hv1 ^. edgeId) h2-  linkEdges h1              (hv1 ^. edgeNext)-  linkEdges h2              (hv0 ^. edgeNext)-  updateFaces (hv0 ^. edgeId) f1-  updateFaces (hv1 ^. edgeId) f2-  modify $ meshFaces %~ M.delete f0-  return (f1,f2)--steinerNodes :: Mesh a -> [VertexId]-steinerNodes Mesh{..} =-  [ v-  | v <- M.keys _meshVertices-  , S.notMember v notSteiner-  ]-  where-    notSteiner = S.fromList-      [ _edgeVertex edge-      | edge <- M.elems _meshEdges-      , _edgeFace edge == _meshOuterFace ]---- findNeighbours :: VertexId -> Mesh a -> [Edge]--- findNeighbours vertex Mesh{..} =---   [ edge---   | edge <- M.elems _meshEdges---   , _edgeVertex edge == vertex---   ]--{--For each internal node:-  * Find neighbours-  * Compute angle-based position-  * Compute laplacian position-  * Update if possible--}-meshSmoothPosition :: Mesh (V2 Double) -> Mesh (V2 Double)-meshSmoothPosition = execState worker-  where-    worker = gets steinerNodes >>= mapM_ smoothVertex--smoothVertex :: VertexId -> MeshM (V2 Double) ()-smoothVertex steiner = do-    self <- _vertexPosition <$> requireVertex steiner-    es <- gets (outgoingEdges steiner)-    vs <- mapM (requireVertex . _edgeVertex) es-    let ps = V.fromList (sortEdges self (map _vertexPosition vs))-        angleBased = angleSmooth self ps-        laplacian  = sum ps ^/ (fromIntegral $ length ps) -- laplacian-    if isValidLocation self ps angleBased-      then modifyVertex (vertexPosition .~ angleBased) steiner-      else if isValidLocation self ps laplacian-        then modifyVertex (vertexPosition .~ laplacian) steiner-        else return ()-  where-    sortEdges :: V2 Double -> [V2 Double] -> [V2 Double]-    sortEdges = sortOn . dir-    -- Direction from south of 'a', to 'a', to 'b'.-    dir :: V2 Double -> V2 Double -> Double-    dir a b = (atan2 (crossZ (V2 0 1) (b - a)) (dot (V2 0 1) (b - a)))---angleSmooth :: V2 Double -> V.Vector (V2 Double) -> V2 Double-angleSmooth origin js = V.sum (V.generate n nth) ^/ V.sum (V.generate n factor)- where-  n = length js-  factor i =-    let n_self   = js V.! i-        n_origin = origin - n_self-        n_prev   = js V.! mod (i - 1) n - n_self-        n_next   = js V.! mod (i + 1) n - n_self-        a1       = acos (dot n_origin n_next / (norm n_origin * norm n_next))-        a2       = acos (dot n_origin n_prev / (norm n_origin * norm n_prev))-        alpha    = a1 + a2-    in  recip (alpha * alpha)-  nth i =-    let V2      x         y = origin-        n_self@(V2 x_0 y_0) = js V.! i-        n_origin            = origin - n_self-        n_prev              = js V.! mod (i - 1) n - n_self-        n_next              = js V.! mod (i + 1) n - n_self-        a1 = acos (dot n_origin n_next / (norm n_origin * norm n_next))-        a2 = acos (dot n_origin n_prev / (norm n_origin * norm n_prev))-        alpha               = a1 + a2-        b                   = (a2 - a1) / 2-        x'                  = x_0 + (x - x_0) * cos b - (y - y_0) * sin b-        y'                  = y_0 + (x - x_0) * sin b + (y - y_0) * cos b-    in  V2 x' y' ^/ (alpha * alpha)--isValidLocation :: V2 Double -> V.Vector (V2 Double) -> V2 Double -> Bool-isValidLocation origin edges newLoc =-  or-      [ isInside origin a b newLoc-      | i <- [0 .. length edges - 1]-      , let a = edges V.! i-            b = edges V.! mod (i + 1) (length edges)-      ]-    && V.toList edges-    == sortOn (dir newLoc) (V.toList edges)-    && minAngle origin edges-    <  minAngle newLoc edges- where-  dir :: V2 Double -> V2 Double -> Double-  dir a b = (atan2 (crossZ (V2 0 1) (b - a)) (dot (V2 0 1) (b - a)))--minAngle :: V2 Double -> V.Vector (V2 Double) -> Double-minAngle origin edges = minimum $ concat-  [ [a1, a2, a3]-  | i <- [0 .. length edges - 1]-  , let a            = edges V.! i-        b            = edges V.! mod (i + 1) (length edges)-        (a1, a2, a3) = triangleAngles origin a b-  ]--flipEdge :: Edge -> MeshM (V2 Double) (FaceId, FaceId)-flipEdge e = do-  e' <- getEdge (e^.edgeTwin)--  v0 <- _vertexPosition <$> requireVertex (e^.edgeVertex)-  v1 <- _vertexPosition <$> requireVertex (e'^.edgeVertex)--  a' <- getEdge (e^.edgeNext)-  b' <- getEdge (e'^.edgeNext)--  v0' <- _vertexPosition <$> requireVertex (a'^.edgeVertex)-  v1' <- _vertexPosition <$> requireVertex (b'^.edgeVertex)--  case lineIntersect (v0, v1) (v0', v1') of-    Nothing -> pure (e^.edgeFace, e'^.edgeFace)-    Just{} -> do-      deleteEdge (e^.edgeId)-      insertEdge (e^.edgeFace) (a'^.edgeVertex) (b'^.edgeVertex)--internalEdges :: Mesh a -> [EdgeId]-internalEdges m =-  [ edge^.edgeId-  | edge <- M.elems (m^.meshEdges)-  , edge^.edgeFace /= m^.meshOuterFace-  , let twin = meshGetEdge (edge^.edgeTwin) m-  , edge^.edgeVertex < twin^.edgeVertex-  , twin^.edgeFace /= m^.meshOuterFace-  ]--outerEdges :: Mesh a -> [EdgeId]-outerEdges m =-  [ edge^.edgeId-  | edge <- M.elems (m^.meshEdges)-  , edge^.edgeFace == m^.meshOuterFace-  ]--longestEdge :: Mesh (V2 Double) -> Mesh (V2 Double) -> EdgeId-longestEdge m1 m2 =-    maximumBy cmp (internalEdges m1)-  where-    cmp e1 e2 =-      compare-        (max (edgeLength e1 m1) (edgeLength e1 m2))-        (max (edgeLength e2 m1) (edgeLength e2 m2))--edgeLength :: EdgeId -> Mesh (V2 Double) -> Double-edgeLength eid m =-  let edge = meshGetEdge eid m-      twin = meshGetEdge (edge^.edgeTwin) m-      p1 = _vertexPosition $ meshGetVertex (edge^.edgeVertex) m-      p2 = _vertexPosition $ meshGetVertex (twin^.edgeVertex) m-  in distance p1 p2--applyCompatible :: (t -> Mesh a -> Maybe (Mesh a)) -> (Mesh a -> [t]) -> Mesh a -> Mesh a -> (Mesh a, Mesh a)-applyCompatible _fn _vs m1 m2-  | _meshIdCounter m1 /= _meshIdCounter m2 = error "invalid ID counter"-applyCompatible fn vs m1 m2 = worker m1 m2 (vs m1)-  where-    worker l r [] = (l,r)-    worker l r (eid:rest) =-      case (,) <$> fn eid l <*> fn eid r of-        Nothing       -> worker l r rest-        Just (l', r') -> worker l' r' rest--delaunayFlip :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))-delaunayFlip = applyCompatible delaunayFlip' internalEdges--delaunayFlip' :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))-delaunayFlip' eid m =-  let edge = meshGetEdge eid m-      twin = meshGetEdge (edge^.edgeTwin) m-      f1 = edge^.edgeFace-      f2 = twin^.edgeFace-      beforeAng = min (faceMinAngle f1 m) (faceMinAngle f2 m)-      ((f1',f2'), mAfter) = runState (flipEdge edge) m-      afterAng = min (faceMinAngle f1' mAfter) (faceMinAngle f2' mAfter)-  in-  if (afterAng < beforeAng) || f1' == f1-    then Nothing-    else Just mAfter--splitInternalEdges :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))-splitInternalEdges = applyCompatible splitInternalEdge internalEdges--splitInternalEdge :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))-splitInternalEdge eid m = evalState worker m-  where-    edgeFaces edge = do-      twin <- getEdge (edge^.edgeTwin)-      return (edge^.edgeFace, twin^.edgeFace)-    worker = do-      edge <- getEdge eid-      twin <- getEdge (edge^.edgeTwin)-      v0 <- _vertexPosition <$> requireVertex (edge^.edgeVertex)-      v1 <- _vertexPosition <$> requireVertex (twin^.edgeVertex)-      let middle = lerp 0.5 v0 v1-      (f1,f2) <- edgeFaces edge-      mBefore <- get-      let beforeAng = min (faceMinAngle f1 mBefore) (faceMinAngle f2 mBefore)-      (f3,f4,f5,f6) <- splitTriangle middle eid-      mAfter <- get-      let afterAng = minimum-            [ faceMinAngle f3 mAfter-            , faceMinAngle f4 mAfter-            , faceMinAngle f5 mAfter-            , faceMinAngle f6 mAfter ]-      return ()-      if (afterAng < beforeAng)-        then return Nothing-        else return (Just mAfter)--splitLongestEdge :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))-splitLongestEdge m1 m2 =-    (splitInternalEdgeForced longest m1, splitInternalEdgeForced longest m2)-  where-    longest = longestEdge m1 m2--splitInternalEdgeForced :: EdgeId -> Mesh (V2 Double) -> Mesh (V2 Double)-splitInternalEdgeForced eid m = execState worker m-  where-    worker = do-      edge <- getEdge eid-      twin <- getEdge (edge^.edgeTwin)-      v0 <- _vertexPosition <$> requireVertex (edge^.edgeVertex)-      v1 <- _vertexPosition <$> requireVertex (twin^.edgeVertex)-      let middle = lerp 0.5 v0 v1-      splitTriangle middle eid--splitOuterEdges :: Mesh (V2 Double) -> Mesh (V2 Double) -> (Mesh (V2 Double), Mesh (V2 Double))-splitOuterEdges = applyCompatible splitOuterEdge outerEdges--splitOuterEdge :: EdgeId -> Mesh (V2 Double) -> Maybe (Mesh (V2 Double))-splitOuterEdge eid m = evalState worker m-  where-    worker = do-      edge <- getEdge eid-      twin <- getEdge (edge^.edgeTwin)-      v0 <- _vertexPosition <$> requireVertex (edge^.edgeVertex)-      v1 <- _vertexPosition <$> requireVertex (twin^.edgeVertex)-      let middle = lerp 0.5 v0 v1-      let f1 = twin^.edgeFace-      mBefore <- get-      let beforeAng = faceMinAngle f1 mBefore-      (f2,f3) <- splitOuterTriangle middle eid-      mAfter <- get-      let afterAng = minimum-            [ faceMinAngle f2 mAfter-            , faceMinAngle f3 mAfter ]-      if (afterAng < beforeAng)-        then return Nothing-        else return (Just mAfter)--renderMesh :: Double -> Mesh (V2 Double) -> SVG-renderMesh radius m = mkGroup-  [ mkGroup-    [ withStrokeColor "black" $-      mkLine (x1, y1) (x2, y2)-    | edge <- M.elems (_meshEdges m)-    , _edgeFace edge /= _meshOuterFace m-    , let twin = meshGetEdge (edge ^. edgeTwin) m-    , let V2 x1 y1 = _vertexPosition (meshGetVertex (_edgeVertex edge) m)-          V2 x2 y2 = _vertexPosition (meshGetVertex (_edgeVertex twin) m)-    ]-  -- , mkGroup-  --   [ mkLinePathClosed-  --     [-  --     |-  --     ] [(Double, Double)] -> Tree-  --   | face <- M.elems _meshFaces-  --   ]-  , mkGroup-    [ translate x y $ withFillColor "red" $ mkCircle radius-    | vertex <- M.elems (_meshVertices m)-    , let V2 x y = _vertexPosition vertex-    ]-  ]--renderMeshEdges :: Mesh (V2 Double) -> SVG-renderMeshEdges mesh@Mesh {..} = mkGroup-  [ mkGroup-    [ mkGroup-      [ withStrokeColorPixel (promotePixel $ viridis $ ang/pi) $-        mkLine (x1, y1) (x2, y2)-      ]-    | edge <- M.elems _meshEdges-    , _edgeFace edge /= _meshOuterFace-    , let twin = meshGetEdge (edge ^. edgeTwin) mesh-          V2 x1 y1 = _vertexPosition (meshGetVertex (_edgeVertex edge) mesh)-          V2 x2 y2 = _vertexPosition (meshGetVertex (_edgeVertex twin) mesh)-          ang = meshEdgeAngle mesh (edge^.edgeId)-    , ang/pi*180 < 2-    ]-  ]--renderMeshSimple :: Double -> Mesh (V2 Double) -> SVG-renderMeshSimple radius Mesh {..} = mkGroup-  [ mkGroup-    [ translate x y $ mkGroup-      [ withFillColor "red" $ mkCircle 0.01-      , scaleToHeight (radius*2) $ center $ latex $ T.pack $ show (_vertexId vertex) ]-    | vertex <- M.elems _meshVertices-    , let V2 x y = _vertexPosition vertex-    ]-  ]--renderMeshColored :: Mesh (V2 Double) -> SVG-renderMeshColored m@Mesh{..} = mkGroup-  [ withFillColorPixel c $ mkLinePathClosed $-    [ (x,y) | V2 x y <- facePositions fid m ]-  | fid <- M.keys _meshFaces-  , let ang = faceMinAngle fid m-        bestAng = pi/3-        score = ang/bestAng-        c = promotePixel $ viridis score-  ]--- facePositions :: FaceId -> Mesh a -> [a]--- faceMinAngle :: FaceId -> Mesh (V2 Double) -> Double--renderMeshStats :: Mesh (V2 Double) -> SVG-renderMeshStats mesh = mkGroup-    [ latex $ T.pack $ printf "Min:  %.1f" (minAng/pi*180)-    , translate 0 (sep*1) $-      latex $ T.pack $ printf "Mean: %.1f" (meanAngle/pi*180)-    , translate 0 (sep*2) $-      latex $ T.pack $ printf "Avg:  %.1f" (avgAngle/pi*180)-    , translate 0 (sep*3) $-      latex $ T.pack $ printf "Max:  %.1f" (maxAngle/pi*180)-    ]-  where-    sep = -1-    angs = meshAngles mesh-    minAng = minimum angs-    meanAngle = L.sort angs !! (length angs `div` 2)-    avgAngle = sum angs / (fromIntegral $ length angs)-    maxAngle = maximum angs--toRigidMesh :: Mesh (V2 Double) -> Mesh (V2 Double) -> Rigid.Mesh-toRigidMesh meshA meshB = Rigid.Mesh-    { meshPointsA = pointsA-    , meshPointsB = pointsB-    , meshOutline = outline-    , meshTriangles = trigs }-  where-    pointsA = V.fromList $ map _vertexPosition $ M.elems (meshA^.meshVertices)-    pointsB = V.fromList $ map _vertexPosition $ M.elems (meshB^.meshVertices)-    outline = V.fromList-      [ fromJust (V.elemIndex v pointsA)-      | eid <- faceEdges (meshA^.meshOuterFace) meshA-      , let edge = meshGetEdge eid meshA-            v = _vertexPosition (meshGetVertex (edge^.edgeVertex) meshA)-      ]-    trigs = V.fromList-      [ (aIdx, bIdx, cIdx)-      | fid <- M.keys (meshA^.meshFaces)-      , fid /= (meshA^.meshOuterFace)-      , let edges = map (`meshGetEdge` meshA) (faceEdges fid meshA)-            vs = map (`meshGetVertex` meshA) $ map _edgeVertex edges-            ps = map _vertexPosition vs-            [aIdx, bIdx, cIdx] = map (fromJust . (`V.elemIndex` pointsA)) ps-            p = mkPolygon (V.fromList $ map (fmap realToFrac) ps)-      , pIsSimple p || error "invalid polygon"-      ]-
− src/Reanimate/Math/EarClip.hs
@@ -1,119 +0,0 @@-module Reanimate.Math.EarClip-  ( earCut-  , earCut'-  , earClip-  , earClip'-  , isEarCorner-  ) where--import           Data.List-import qualified Data.Set                   as Set-import           Data.Tuple--import           Reanimate.Math.Common-import           Reanimate.Math.Triangulate--import qualified Data.Vector                as V-import qualified Geometry.Earcut            as C-import           Linear.V2---- import Debug.Trace--earCut :: (Real a) => Ring a -> Triangulation-earCut = last . earCut'--earCut' :: (Real a) => Ring a -> [Triangulation]-earCut' p =-  map (edgesToTriangulation (ringSize p)) $ inits $ nub-  [ if fst pair < snd pair then pair else swap pair-  | (a,b,c) <- V.toList (C.earcut lst)-  , pair <- [(a,b), (a,c), (b,c)]-  , fst pair /= (snd pair+1) `mod` ringSize p-  , fst pair /= (snd pair-1) `mod` ringSize p-  ]-  where-    lst = [ (x,y) | V2 x y <- V.toList $ V.map (fmap realToFrac) $ ringUnpack p ]---- Triangulation by ear clipping. O(n^2)-earClip :: (Fractional a, Ord a) => Ring a -> Triangulation-earClip = last . earClip'--earClip' :: (Fractional a, Ord a) => Ring a -> [Triangulation]-earClip' p = map (edgesToTriangulation $ ringSize p) $ inits $-  let ears = Set.fromList [ i-             | i <- elts-             , isEarCorner p elts (mod (i-1) n) i (mod (i+1) n) ]-  in worker ears (mkQueue elts)-  where-    n = ringSize p-    elts = [0 .. n-1]-    -- worker :: Set.Set Int -> PolyQueue Int -> [(P,P)]-    worker _ears queue | isSimpleQ queue = []-    worker ears queue-      | x `Set.member` ears =-        let dq = dropQ queue-            v0 = prevQ 1 queue-            v1 = prevQ 0 queue-            v3 = peekQ dq-            v4 = peekQ (nextQ dq)-            e1 = if isEarCorner p (toList dq) v0 v1 v3-                  then Set.insert v1 ears-                  else Set.delete v1 ears-            e2 = if isEarCorner p (toList dq) v1 v3 v4-                  then Set.insert v3 e1-                  else Set.delete v3 e1-        in (v1,v3) : worker e2 dq-      | otherwise = worker ears (nextQ queue)-      where-        x = peekQ queue--data PolyQueue a = PolyQueue a [a] [a] [a]---- sizeQ :: PolyQueue a -> Int--- sizeQ (PolyQueue _ a b _) = 1 + length a + length b--mkQueue :: [a] -> PolyQueue a-mkQueue (x:xs) = PolyQueue x xs [] (reverse (x:xs))-mkQueue []     = error "mkQueue: empty"--toList :: PolyQueue a -> [a]-toList (PolyQueue e a b _) = e : a ++ b--isSimpleQ :: PolyQueue a -> Bool-isSimpleQ (PolyQueue _ xs ys _) =-  case xs ++ ys of-    [_,_] -> True-    _     -> False--peekQ :: PolyQueue a -> a-peekQ (PolyQueue e _ _ _) = e--nextQ :: PolyQueue a -> PolyQueue a-nextQ (PolyQueue x [] ys p)     =-  let (y:xs) = reverse (x:ys)-  in PolyQueue y xs [] (x:p)-nextQ (PolyQueue x (y:xs) ys p) = PolyQueue y xs (x:ys) (x:p)--dropQ :: PolyQueue a -> PolyQueue a-dropQ (PolyQueue _ [] ys p)    =-  let (x:xs) = reverse ys-  in PolyQueue x xs [] p-dropQ (PolyQueue _ (x:xs) ys p) = PolyQueue x xs ys p--prevQ :: Int -> PolyQueue a -> a-prevQ nth (PolyQueue _ _ _ p) = p!!nth---- O(n)--- Returns true if ac can be cut from polygon. That is, true if 'b' is an ear.--- isEarCorner polygon a b c = True iff ac can be cut-isEarCorner :: (Fractional a, Ord a) => Ring a -> [Int] -> Int -> Int -> Int -> Bool-isEarCorner p polygon a b c =-    isLeftTurn aP bP cP &&-    -- If it is a right turn then the line ac will be outside the polygon-    and [ not (isInside aP bP cP (ringAccess p k))-    | k <- polygon, k /= a && k /= b && k /= c-    ]-  where-    aP = ringAccess p a-    bP = ringAccess p b-    cP = ringAccess p c
src/Reanimate/Math/Polygon.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE BangPatterns    #-}+{-# LANGUAGE ConstraintKinds #-}+{-# OPTIONS_HADDOCK hide #-} module Reanimate.Math.Polygon   ( APolygon(..)   , Polygon@@ -5,6 +8,7 @@   , P   , mkPolygon     -- :: (Fractional a, Ord a) => V.Vector (V2 a) -> APolygon a   , mkPolygonFromRing -- :: (Fractional a, Ord a) => Ring a -> APolygon a+  , castPolygon   -- :: (Real a, Fractional b, Ord a) => APolygon a -> APolygon b   , pParent       -- :: Polygon -> Int -> Int -> Int   , pSetOffset    -- :: APolygon a -> Int -> APolygon a   , pAdjustOffset -- :: APolygon a -> Int -> APolygon a@@ -33,7 +37,10 @@   , pCircumference   -- :: (Real a, Fractional a) => APolygon a -> a   , pCircumference'  -- :: (Real a, Fractional a) => APolygon a -> Double   , pAddPoints       -- :: Int -> Polygon -> Polygon+  , pAddPointsRestricted -- :: [Int] -> Int -> Polygon -> Polygon+  , pAddPointsBetween -- :: (Fractional a, Ord a, Real a) => (Int, Int) -> Int -> APolygon a -> APolygon a   , pRayIntersect    -- :: Polygon -> (Int, Int) -> (Int,Int) -> Maybe (V2 Rational)+  , pOverlap         -- :: Polygon -> Polygon -> Polygon   , pCuts         -- :: Polygon -> [(Polygon,Polygon)]   , pCutEqual     -- :: Polygon -> (Polygon, Polygon)   -- * Triangulation@@ -77,6 +84,7 @@   , pCopy       -- :: Polygon -> Polygon   , pGenerate   -- :: [(Double, Double)] -> Polygon   , pUnGenerate -- :: Polygon -> [(Double, Double)]+  , Epsilon   ) where  -- import           Control.Exception@@ -91,7 +99,7 @@ import           Linear.V2 import           Linear.Vector import           Reanimate.Math.Common-import           Reanimate.Math.EarClip+-- import           Reanimate.Math.EarClip import           Reanimate.Math.SSSP import           Reanimate.Math.Triangulate @@ -119,14 +127,16 @@ instance Hashable a => Hashable (APolygon a) where   hashWithSalt s p = V.foldl' hashWithSalt s (polygonPoints p) -instance (Real a, Fractional a, Ord a, Serialize a) => Serialize (APolygon a) where+instance (PolyCtx a, Serialize a) => Serialize (APolygon a) where   put = put . V.toList . polygonPoints   get = mkPolygon . V.fromList <$> get  pRing :: APolygon a -> Ring a pRing = ringPack . polygonPoints -mkPolygon :: (Real a, Fractional a, Ord a) => V.Vector (V2 a) -> APolygon a+type PolyCtx a = (Real a, Fractional a, Epsilon a)++mkPolygon :: PolyCtx a => V.Vector (V2 a) -> APolygon a mkPolygon points = Polygon     { polygonPoints = points     , polygonOffset = 0@@ -136,17 +146,20 @@   where     n = length points     ring = ringPack points-    trig = earCut ring+    trig = triangulate ring       -- earClip ring -mkPolygonFromRing :: (Real a, Fractional a, Ord a) => Ring a -> APolygon a+castPolygon :: (PolyCtx a, PolyCtx b) => APolygon a -> APolygon b+castPolygon = mkPolygon . V.map (fmap realToFrac) . polygonPoints++mkPolygonFromRing :: PolyCtx a => Ring a -> APolygon a mkPolygonFromRing = mkPolygon . ringUnpack  pUnsafeMap :: (Ring a -> Ring a) -> APolygon a -> APolygon a pUnsafeMap fn p = p{ polygonPoints = ringUnpack (fn (pRing p)) }  -- pParent p i j = shortest-path parent from j to i-pParent :: Polygon -> Int -> Int -> Int+pParent :: APolygon a -> Int -> Int -> Int pParent p i j =     (sTree V.! mod (j + polygonOffset p) n - polygonOffset p) `mod` n   where@@ -164,6 +177,7 @@ pAdjustOffset p offset =   p { polygonOffset = (polygonOffset p + offset) `mod` pSize p } +{-# INLINE pSize #-} pSize :: APolygon a -> Int pSize = length . polygonPoints @@ -316,9 +330,10 @@ --     findSmallest = minimumBy (comparing area2X) --     shareEdge p1 p2 = -+{-# INLINE pAccess #-} pAccess :: APolygon a -> Int -> V2 a-pAccess p i = polygonPoints p V.! ((polygonOffset p + i) `mod` pSize p)+pAccess p i = -- polygonPoints p V.! ((polygonOffset p + i) `mod` pSize p)+  polygonPoints p `V.unsafeIndex` ((polygonOffset p + i) `mod` pSize p)  triangle :: Polygon triangle = mkPolygon $ V.fromList [V2 1 1, V2 0 0, V2 2 0]@@ -503,10 +518,10 @@           in lerp 0.99999 this prev         else pAccess p n -pCycles :: Polygon -> [Polygon]+pCycles :: APolygon a -> [APolygon a] pCycles p = map (pAdjustOffset p) [0 .. pSize p-1] -pCycle :: (Real a, Fractional a, Ord a) => APolygon a -> Double -> APolygon a+pCycle :: PolyCtx a => APolygon a -> Double -> APolygon a pCycle p 0 = p pCycle p t = mkPolygon $ worker 0 0   where@@ -526,7 +541,7 @@     len = pCircumference' p     limit = t * len -pCentroid :: Polygon -> V2 Rational+pCentroid :: Fractional a => APolygon a -> V2 a pCentroid p = V2 cx cy   where     a = pArea p@@ -535,13 +550,29 @@     fnX (V2 x y) (V2 x' y') = (x+x')*(x*y' - x'*y)     fnY (V2 x y) (V2 x' y') = (y+y')*(x*y' - x'*y) -pMapEdges :: (V2 Rational -> V2 Rational -> a) -> Polygon -> V.Vector a-pMapEdges fn p = V.generate (pSize p) $ \i ->-  fn (pAccess p i) (pAccess p $ i+1)+{-# INLINE pMapEdges #-}+pMapEdges :: (V2 a -> V2 a -> b) -> APolygon a -> V.Vector b+pMapEdges fn p = V.generate n $ \i ->+  if i == n-1+    then fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` 0)+    else fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` (i+1))+  where+    n = pSize p+    arr = polygonPoints p -pArea :: Polygon -> Rational+{-# SPECIALIZE pArea :: APolygon Double -> Double #-}+{-# SPECIALIZE pArea :: APolygon Rational -> Rational #-}+pArea :: (Fractional a) => APolygon a -> a pArea p =-  0.5 * V.sum (pMapEdges (\(V2 x y) (V2 x' y') -> x*y' - x'*y) p)+  -- 0.5 * V.sum (pMapEdges (\(V2 x y) (V2 x' y') -> x*y' - x'*y) p)+  0.5 * worker 0 0+  where+    fn (V2 x y) (V2 x' y') = x*y' - x'*y+    arr = polygonPoints p+    worker !acc i+      | i == pSize p - 1 = acc + fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` 0)+      | otherwise =+        worker (acc + fn (arr `V.unsafeIndex` i) (arr `V.unsafeIndex` (i+1))) (i+1)  pCircumference :: (Real a, Fractional a) => APolygon a -> a pCircumference p = sum@@ -555,7 +586,7 @@   -- Add points by splitting the longest lines in half repeatedly.-pAddPoints :: Int -> Polygon -> Polygon+pAddPoints :: PolyCtx a => Int -> APolygon a -> APolygon a pAddPoints n p | n <= 0 = p pAddPoints n p = pAddPoints (n-1) $     mkPolygon $ V.fromList $ concatMap worker [0 .. pSize p-1]@@ -572,6 +603,45 @@       distSquared (pAccess p a) (pAccess p $ a+1) `compare`       distSquared (pAccess p b) (pAccess p $ b+1) +pAddPointsRestricted :: PolyCtx a => [(V2 a, V2 a)] -> Int -> APolygon a -> APolygon a+pAddPointsRestricted _immutableEdges n p | n <= 0 = p+pAddPointsRestricted immutableEdges n p = pAddPointsRestricted immutableEdges (n-1) $+    mkPolygon $ V.fromList $ concatMap worker [0 .. pSize p-1]+  where+    isImmutable idx =+      (pAccess p idx, pAccess p $ idx+1) `elem` immutableEdges ||+      (pAccess p $ idx+1, pAccess p idx) `elem` immutableEdges+    worker idx+      | idx == longestEdge && not (isImmutable idx) =+        let start = pAccess p idx+            end = pAccess p $ idx+1+            middle = lerp 0.5 end start+        in [start, middle]+      | otherwise = [pAccess p idx]+    longestEdge = maximumBy cmpLength [0 .. pSize p-1]+    cmpLength a _ | isImmutable a = LT+    cmpLength _ b | isImmutable b = GT+    cmpLength a b =+      distSquared (pAccess p a) (pAccess p $ a+1) `compare`+      distSquared (pAccess p b) (pAccess p $ b+1)++pAddPointsBetween :: PolyCtx a => (Int, Int) -> Int -> APolygon a -> APolygon a+pAddPointsBetween _ n p | n <= 0 = p+pAddPointsBetween (i,l) n p = pAddPointsBetween (i,l+1) (n-1) $+    mkPolygon $ V.fromList $ concatMap worker [0 .. pSize p-1]+  where+    worker idx+      | idx == longestEdge =+        let start = pAccess p idx+            end = pAccess p $ idx+1+            middle = lerp 0.5 end start+        in [start, middle]+      | otherwise = [pAccess p idx]+    longestEdge = maximumBy cmpLength [i .. i+l-1]+    cmpLength a b =+      distSquared (pAccess p a) (pAccess p $ a+1) `compare`+      distSquared (pAccess p b) (pAccess p $ b+1)+ -- addPoints :: Int -> Polygon -> Polygon -- addPoints n p = mkPolygon $ V.fromList $ worker n 0 (map (pAccess p) [0..s]) --   where@@ -603,11 +673,11 @@     fn (V2 x1 y1) (V2 x2 y2) = (x2-x1)*(y2+y1)  {-# INLINE pRayIntersect #-}-pRayIntersect :: Polygon -> (Int, Int) -> (Int,Int) -> Maybe (V2 Rational)+pRayIntersect :: PolyCtx a => APolygon a -> (Int, Int) -> (Int,Int) -> Maybe (V2 a) pRayIntersect p (a,b) (c,d) =   rayIntersect (pAccess p a, pAccess p b) (pAccess p c, pAccess p d) -pCuts :: Polygon -> [(Polygon,Polygon)]+pCuts :: (Real a, Fractional a, Epsilon a) => APolygon a -> [(APolygon a,APolygon a)] pCuts p =   [ pCutAt (pAdjustOffset p i) (j-i)   | i <- [0 .. pSize p-1 ]@@ -615,26 +685,41 @@   , (j+1) `mod` pSize p /= i   , pParent p i j == i ] -pCutEqual :: Polygon -> (Polygon, Polygon)+pCutEqual :: PolyCtx a => APolygon a -> (APolygon a, APolygon a) pCutEqual p =     fromMaybe (p,p) $ listToMaybe $ sortOn f $ pCuts p   where     f (a,b) = abs (pArea a - pArea b)  -- FIXME: This should be more efficient-pCutAt :: Polygon -> Int -> (Polygon, Polygon)+pCutAt :: PolyCtx a => APolygon a -> Int -> (APolygon a, APolygon a) pCutAt p i = (mkPolygon $ V.fromList left, mkPolygon $ V.fromList right)   where     n     = pSize p     left  = map (pAccess p) [0 .. i]     right = map (pAccess p) (0:[i..n-1]) -+pOverlap :: PolyCtx a => APolygon a -> APolygon a -> APolygon a+pOverlap a b = mkPolygon $ V.fromList $ clearDups $ concatMap edgeIntersect [0 .. pSize a-1]+  where+    clearDups (x:y:xs)+      | x == y = clearDups (y:xs)+      | otherwise = x : clearDups (y:xs)+    clearDups xs = xs+    edgeIntersect edge =+      sortOn (distSquared (pAccess a edge)) $ catMaybes+      [ lineIntersect (aP, aP') (bP, bP')+      | i <- [0 .. pSize b-1]+      , let aP = pAccess a edge+            aP' = pAccess a (edge+1)+            bP = pAccess b i+            bP' = pAccess b (i+1)+      ]  --------------------------------------------------------- -- SSSP visibility and SSSP windows -ssspVisibility :: Polygon -> Polygon+ssspVisibility :: PolyCtx a => APolygon a -> APolygon a ssspVisibility p = mkPolygon $     V.fromList $ clearDups $ go [0 .. pSize p-1] -- ([root..pSize p-1]  ++ [0 .. root-1])   where
− src/Reanimate/Math/Render.hs
@@ -1,231 +0,0 @@-module Reanimate.Math.Render where--import           Codec.Picture.Types-import           Control.Monad--- import           Data.List-import qualified Data.Text                  as T-import qualified Data.Vector                as V-import           Linear.V2-import           Reanimate.Animation-import           Reanimate.ColorMap--- import           Reanimate.Constants-import           Reanimate.LaTeX--- import           Reanimate.Math.Compatible--- import           Reanimate.Math.EarClip-import           Reanimate.Math.Polygon-import           Reanimate.Math.Common-import           Reanimate.Math.SSSP-import           Reanimate.Math.Triangulate--- import           Reanimate.Math.Visibility-import           Reanimate.Scene-import           Reanimate.Svg----- drawVisibility :: Polygon -> Animation--- drawVisibility p' = mkAnimation 5 $ \t ->---   let p = cyclePolygon p' (t::Double) in---   centerUsing (polygonShape p) $---   mkGroup---   [ withFillColor "grey" $ polygonShape p---   , withFillColor "grey" $ polygonDots p---   , withFillColor "white" $ mkLinePathClosed---     [ (x,y) | V2 x y <- visibility (map (fmap realToFrac) $ V.toList $ polygonPoints p) ]---   , let V2 x y = fmap realToFrac $ pAccess p 0 in---     translate x y $ withFillColor "red" $ mkCircle 0.1---   -- , withFillColor "blue" $ latex $ T.pack $ show (t)---   ]---- drawSSSPVisibility :: Polygon -> Animation--- drawSSSPVisibility p' = mkAnimation 5 $ \t ->---   let p = pSetOffset p' (round $ t*(fromIntegral $ pSize p'-1))---       vis = ssspVisibility p in---   centerUsing (polygonShape p) $---   mkGroup---   [ withFillColor "grey" $ polygonShape p---   -- , withFillColor "grey" $ polygonDots p---   -- , withFillColor "white" $ polygonShape vis---   , let V2 x y = fmap realToFrac $ pAccess p 0 in---     translate x y $ withFillColor "red" $ mkCircle 0.1---   -- , withFillColor "blue" $ latex $ T.pack $ show (t)---   ]---- drawSSSPVisibilityFast :: Polygon -> Animation--- drawSSSPVisibilityFast p' = mkAnimation 5 $ \t ->---   let root = min (pSize p-1) $ (floor $ t*(fromIntegral $ pSize p))---       p = pSetOffset p' root---       vis = ssspVisibility p in---   -- centerUsing (polygonShape p) $---   mkGroup---   [ withFillColor "grey" $ polygonShape p---   , withFillColor "white" $ polygonShape vis---   , withFillColor "grey" $ polygonNumDots p---   , let V2 x y = fmap realToFrac $ pAccess p 0 in---     translate x y $ withFillColor "red" $ mkCircle 0.09---   -- , withFillColor "blue" $ latex $ T.pack $ show (t)---   ]---- drawCompatible :: Polygon -> Polygon -> Animation--- drawCompatible a b = sceneAnimation $ do---   newSpriteSVG $ translate (-3) 0 $ mkGroup---     [ withFillColor "grey" $ polygonShape a---     , withFillColor "grey" $ polygonNumDots a---     ]---   newSpriteSVG $ translate (3) 0 $ mkGroup---     [ withFillColor "grey" $ polygonShape b---     , withFillColor "grey" $ polygonNumDots b---     ]---   let compat = compatiblyTriangulateP a b---   forM_ compat $ \(l, r) -> do---     fork $ play $ staticFrame 1 $---       translate (-3) 0 $ withStrokeColor "white" $ withStrokeWidth (defaultStrokeWidth*0.2) $---       withFillOpacity 0 $ polygonShape l---     fork $ play $ staticFrame 1 $---       translate (3) 0 $ withStrokeColor "white" $ withStrokeWidth (defaultStrokeWidth*0.2) $---       withFillOpacity 0 $ polygonShape r---- drawWindow :: Polygon -> SVG--- drawWindow p =---   let mWins = ssspWindows p---       in---   mkGroup---   [ mkGroup---     [ mkLine (x1,y1) (x2,y2)---     | (a,b) <- mWins---     , let V2 x1 y1 = realToFrac <$> a---           V2 x2 y2 = realToFrac <$> b---     ]---   ]---- drawWindowOverlap :: Polygon -> Int -> Int -> SVG--- drawWindowOverlap p a b =---   let aWins = ssspWindows (modOffset p a)---       bWins = ssspWindows (modOffset p b)---       in---   mkGroup---   [ mkGroup $---     [ withStrokeColor "green" $mkGroup---       [ mkLine (x1,y1) (x2,y2)---       , mkLine (x1',y1') (x2',y2') ]---     | (a,b) <- aWins---     , (i,j) <- bWins---     , a == i || a == j || b == i || b == j---     , not (sort [a,b] == sort [i,j])---     , let V2 x1 y1 = realToFrac <$> a---           V2 x2 y2 = realToFrac <$> b---           V2 x1' y1' = realToFrac <$> i---           V2 x2' y2' = realToFrac <$> j---     ]---   ]--polygonShape :: Polygon -> SVG-polygonShape p = mkLinePathClosed-  [ (x,y) | V2 x y <- map (fmap realToFrac) $ V.toList (polygonPoints p) ]--polygonDots :: Polygon -> SVG-polygonDots p = mkGroup-  [ translate x y $ mkCircle 0.1 | V2 x y <- V.toList $ V.map (fmap realToFrac) $ polygonPoints p ]--polygonNumDots :: Polygon -> SVG-polygonNumDots p = mkGroup $ reverse-    [ mkGroup-      [ colored n $-        translate x y $ mkCircle circR-      , withFillColor "black" $-        translate x y $ ppNum n ]-    | n <- [0..pSize p-1]-    , let V2 x y = realToFrac <$> pAccess p n ]-  where-    circR = 0.05-    colored n =-      let c = promotePixel $ turbo (fromIntegral (n+2) / fromIntegral (pSize p-1+2))-      in withStrokeColorPixel c . withFillColorPixel c-    ppNum n =-      scaleToSize (circR*1.7) (circR*1.5) $-      center $ latex $ T.pack $ "\\texttt{" ++ show n ++ "}"---- drawSSSP :: Polygon -> (Polygon -> SSSP) -> Animation--- drawSSSP p gen = mkAnimation 5 $ \t -> centerUsing outline $---   let p' = cyclePolygons p !! (round $ t*(fromIntegral $ pSize p-1)) in---   mkGroup---   [ outline---   , renderSSSP p' (gen p')---   , withFillColor "grey" $ polygonNumDots $ p'---   ]---   where---     outline =---       withFillColor "grey" $ mkLinePathClosed---         [ (x,y) | V2 x y <- map (fmap realToFrac) (V.toList (polygonPoints p)  ++ [pAccess p 0]) ]--drawSSSP :: Polygon -> Animation-drawSSSP p = mkAnimation 5 $ \t -> centerUsing (polygonShape p) $-  let root = round $ t*fromIntegral (pSize p-1)-      sTree = polygonSSSP p V.! root in-  mkGroup-  [ polygonShape p-  , renderSSSP p sTree-  , polygonNumDots p-  ]---- Inline such that 'trees' is only computed once.-{-# INLINE drawSSSPNaive #-}-drawSSSPNaive :: Polygon -> Animation-drawSSSPNaive p = mkAnimation 5 $ \t -> centerUsing (polygonShape p) $-  let root = round $ t*fromIntegral (pSize p-1) in-  mkGroup-  [ polygonShape p-  , renderSSSP p (trees !! root)-  , polygonNumDots p-  ]-  where-    trees =-      [ naive $ pRing $ pCopy $ pSetOffset p i-      | i <- [0 .. pSize p-1]]--renderSSSP :: Polygon -> SSSP -> SVG-renderSSSP p s = withFillOpacity 0 $ withStrokeColor "white" $ mkGroup-  [ mkLinePath (lineFrom i)-  | i <- [0 .. length s-1] ]-  where-    lineFrom i =-      let V2 ax ay = realToFrac <$> pAccess p i-          next = (s V.! i)-      in (ax,ay) : if next == i then [] else lineFrom next--drawTriangulation :: Polygon -> (Ring Rational -> [Triangulation]) -> Animation-drawTriangulation p gen = sceneAnimation $-  forM_ (gen $ pRing p) $ \t -> play $ staticFrame 1 $ renderTriangulation p t--renderTriangulation :: Polygon -> Triangulation -> SVG-renderTriangulation p t = center $ mkGroup-  [ withFillColor "grey" $ polygonShape p-  , withStrokeColor "white" $ mkGroup $ concat-    [ [ mkLine (ax,ay) (bx,by) ]-    | i <- [0..pSize p-1]-    , y <- t V.! i-    , let V2 ax ay = realToFrac <$> rawAccess i-          V2 bx by = realToFrac <$> rawAccess y-    ]-  , withFillColor "grey" $ polygonNumDots p-  ]-  where-    rawAccess x = polygonPoints p V.! x--renderDual :: Ring Rational -> Dual -> SVG-renderDual ring d = case d of-    Dual (a,b,c) l r -> mkGroup-      [ withFillColor "blue" $ mkTrig a b c-      , worker c a l-      , worker b c r-      ]-  where-    mkTrig a b c =-      let V2 x1 y1 = realToFrac <$> ringAccess ring a-          V2 x2 y2 = realToFrac <$> ringAccess ring b-          V2 x3 y3 = realToFrac <$> ringAccess ring c-      in mkLinePathClosed [ (x1, y1), (x2,y2), (x3,y3) ]-    worker _p1 _p2 EmptyDual = mkGroup []-    worker p1 p2 (NodeDual x l r) = mkGroup-      [ mkTrig p1 p2 x-      , worker x p2 l-      , worker p1 x r-      ]
src/Reanimate/Math/SSSP.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleInstances     #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_HADDOCK hide #-} module Reanimate.Math.SSSP   ( -- * Single-Source-Shortest-Path     SSSP@@ -276,7 +277,7 @@ -- dualRoot (Dual (a,_,_) _ _) = a  -- O(n*ln n), could be O(n) if I could figure out how to use fingertrees...-sssp :: (Fractional a, Ord a) => Ring a -> Dual -> SSSP+sssp :: (Fractional a, Ord a, Epsilon a) => Ring a -> Dual -> SSSP sssp p d = toSSSP $     case d of       Dual (a,b,c) l r ->
− src/Reanimate/Math/Smooth.hs
@@ -1,291 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-module Reanimate.Math.Smooth-  ( steinerPoints-  , renderMesh-  , renderAMesh-  , smoothMesh-  , smoothStep-  , angleSmooth-  , meshMinAngle-  , splitMeshEdges-  )-where--import qualified Data.IntSet                   as ISet-import           Control.Monad-import           Control.Monad.ST-import           Data.STRef-import           Data.List-import qualified Data.Vector                   as V-import qualified Data.Vector.Mutable           as MV-import           Linear.V2-import           Linear.Metric-import           Linear.Vector-import           Reanimate.Animation-import           Reanimate.Constants-import           Reanimate.Math.Common-import           Reanimate.Math.Polygon-import           Reanimate.Math.Render-import           Reanimate.Math.Triangulate-import           Reanimate.Morph.Rigid-import           Reanimate.Svg--steinerPoints :: Polygon -> [Polygon] -> [V2 Rational]-steinerPoints outerPolygon = concatMap-  (V.toList . V.filter isSteiner . polygonPoints)-  where isSteiner p = V.notElem p (polygonPoints outerPolygon)--- compatiblyTriangulateP :: Polygon -> Polygon -> [(Polygon, Polygon)]--renderMesh :: Polygon -> [Polygon] -> SVG-renderMesh outerPolygon innerPolygons = mkGroup-  [ mkGroup-    [ withFillOpacity 1-      $ withStrokeWidth (defaultStrokeWidth * 0)-      $ withStrokeColor "black"-      $ withFillColor "lightgreen"-      $ polygonShape p-    | p <- innerPolygons-    ]-  , mkGroup-    [ withFillColor "red" $ aroundCenter (scale 0.2) $ drawPoint-        (realToFrac <$> p)-    | p <- steiners-    ]-  ]-  where steiners = steinerPoints outerPolygon innerPolygons--renderAMesh :: Mesh -> SVG-renderAMesh m = mkGroup-  [ translate (-1.5) 0 $ renderMesh-    (mkPolygon outlineA)-    [ mkPolygon $ V.fromList $ map (fmap realToFrac)-                                   [pA V.! a, pA V.! b, pA V.! c]-    | (a, b, c) <- V.toList (meshTriangles m)-    ]-  , translate (1.5) 0 $ renderMesh-    (mkPolygon outlineB)-    [ mkPolygon $ V.fromList $ map (fmap realToFrac)-                                   [pB V.! a, pB V.! b, pB V.! c]-    | (a, b, c) <- V.toList (meshTriangles m)-    ]-  ]- where-  pA       = meshPointsA m-  pB       = meshPointsB m-  outlineA = V.map (\i -> realToFrac <$> meshPointsA m V.! i) (meshOutline m)-  outlineB = V.map (\i -> realToFrac <$> meshPointsB m V.! i) (meshOutline m)---drawPoint :: V2 Double -> SVG-drawPoint (V2 x y) = translate x y $ mkCircle 0.1----- Plot angles for a mesh.--- Plot min-angles for meshes as they are iteratively smoothed.---- Smoothing algorithm:---   For each steiner point:---      Find desired position from each vertex, take average, update position.---   For each edge:---     Does flipping it increase min-angle in both meshes?---       If yes, do it.---   For long edges:---     Cut in half:---       Cut the two connected faces in half.--- all points--- steiner points-----{--data Mesh = Mesh-  { meshPointsA :: Vector P-  , meshPointsB :: Vector P-  , meshOutline :: Vector Int-  -- , meshSteiner :: Vector Int-  , meshTriangles :: Vector RelTrig }---}-smoothMesh :: Mesh -> [Mesh]-smoothMesh m = runST $ do-  trigs <- edgesToTriangulationM (length $ meshPointsA m) $ concat-    [ [(a, b), (b, c), (c, a)] | (a, b, c) <- V.toList (meshTriangles m) ]-  pA <- V.thaw $ V.map (fmap realToFrac) (meshPointsA m)-  pB <- V.thaw $ V.map (fmap realToFrac) (meshPointsB m)-  replicateM 120 $ do-    smoothStep trigs pA steiner-    smoothStep trigs pB steiner-    newA <- V.freeze pA-    newB <- V.freeze pB-    return $ m { meshPointsA = newA, meshPointsB = newB }- where-  steiner = V.fromList-    [ i | i <- [0 .. length (meshPointsA m) - 1], V.notElem i (meshOutline m) ]---smoothStep-  :: forall s-   . V.MVector s [Int]-  -> V.MVector s (V2 Double)-  -> V.Vector Int-  -> ST s ()-smoothStep triangulation pts steiner =-  forM_ [0 .. length steiner - 1] {-length steiner-1-}-                                  $ \s_i -> do-    let i = steiner V.! s_i-    pt    <- MV.read pts i-    edges <- V.fromList <$> (sortEdges pt =<< MV.read triangulation i)-    let angleBased = angleSmooth pt edges-        laplacian  = sum edges ^/ (fromIntegral $ length edges) -- laplacian-    -- unsafeIOToST $ hPrint stderr (pt, edges, newX)-    if isValidLocation pt edges angleBased-      then MV.write pts i angleBased-      else if isValidLocation pt edges laplacian-        then MV.write pts i laplacian-        else return ()- where-  sortEdges :: V2 Double -> [Int] -> ST s [V2 Double]-  sortEdges pt edges = do-    edgePoints <- mapM (MV.read pts) edges-    return $ sortOn (dir pt) edgePoints-  -- Direction from south of 'a', to 'a', to 'b'.-  dir :: V2 Double -> V2 Double -> Double-  dir a b = (atan2 (crossZ (V2 0 1) (b - a)) (dot (V2 0 1) (b - a)))--angleSmooth :: V2 Double -> V.Vector (V2 Double) -> V2 Double-angleSmooth origin js = V.sum (V.generate n nth) ^/ V.sum (V.generate n factor)- where-  n = length js-  factor i =-    let n_self   = js V.! i-        n_origin = origin - n_self-        n_prev   = js V.! mod (i - 1) n - n_self-        n_next   = js V.! mod (i + 1) n - n_self-        a1       = acos (dot n_origin n_next / (norm n_origin * norm n_next))-        a2       = acos (dot n_origin n_prev / (norm n_origin * norm n_prev))-        alpha    = a1 + a2-    in  recip (alpha * alpha)-  nth i =-    let V2      x         y = origin-        n_self@(V2 x_0 y_0) = js V.! i-        n_origin            = origin - n_self-        n_prev              = js V.! mod (i - 1) n - n_self-        n_next              = js V.! mod (i + 1) n - n_self-        a1 = acos (dot n_origin n_next / (norm n_origin * norm n_next))-        a2 = acos (dot n_origin n_prev / (norm n_origin * norm n_prev))-        alpha               = a1 + a2-        b                   = (a2 - a1) / 2-        x'                  = x_0 + (x - x_0) * cos b - (y - y_0) * sin b-        y'                  = y_0 + (x - x_0) * sin b + (y - y_0) * cos b-    in  V2 x' y' ^/ (alpha * alpha)--isValidLocation :: V2 Double -> V.Vector (V2 Double) -> V2 Double -> Bool-isValidLocation origin edges newLoc =-  or-      [ isInside origin a b newLoc-      | i <- [0 .. length edges - 1]-      , let a = edges V.! i-            b = edges V.! mod (i + 1) (length edges)-      ]-    && V.toList edges-    == sortOn (dir newLoc) (V.toList edges)-    && minAngle origin edges-    <  minAngle newLoc edges- where-  dir :: V2 Double -> V2 Double -> Double-  dir a b = (atan2 (crossZ (V2 0 1) (b - a)) (dot (V2 0 1) (b - a)))--minAngle :: V2 Double -> V.Vector (V2 Double) -> Double-minAngle origin edges = minimum $ concat-  [ [a1, a2, a3]-  | i <- [0 .. length edges - 1]-  , let a            = edges V.! i-        b            = edges V.! mod (i + 1) (length edges)-        (a1, a2, a3) = triangleAngles origin a b-  ]--meshMinAngle :: Mesh -> (Double, Double)-meshMinAngle m =-  ( minimum $ concat-    [ [a1, a2, a3]-    | (a, b, c) <- V.toList (meshTriangles m)-    , let (a1, a2, a3) = triangleAngles (meshPointsA m V.! a)-                                        (meshPointsA m V.! b)-                                        (meshPointsA m V.! c)-    ]-  , minimum $ concat-    [ [a1, a2, a3]-    | (a, b, c) <- V.toList (meshTriangles m)-    , let (a1, a2, a3) = triangleAngles (meshPointsB m V.! a)-                                        (meshPointsB m V.! b)-                                        (meshPointsB m V.! c)-    ]-  )---- Smooth:---   Find all edges---   Sort counter-clockwise from 12 o'clock. (ie direction from south)---   Compute new position.---   Update position.--{--data Mesh = Mesh-  { meshPointsA :: Vector P-  , meshPointsB :: Vector P-  , meshOutline :: Vector Int-  -- , meshSteiner :: Vector Int-  , meshTriangles :: Vector RelTrig }--}--- edge: a to b--- opposite points: c and d--- delete edge (a,b)--- insert point p--- insert edge (a,p)--- insert edge (p,b)--- insert edge (c,p)--- insert edge (p,d)-splitMeshEdges :: Double -> Mesh -> Mesh-splitMeshEdges maxLen mesh = runST $ do-  t <- MV.replicate (n * 2) []-  forM_ (V.toList (meshTriangles mesh)) $ \(a, b, c) -> do-    insertEdge t a b-    insertEdge t b c-    insertEdge t a c-  forM_ [0 .. n - 1] $ \i -> MV.modify t (ISet.toList . ISet.fromList) i-  triangulation <- V.freeze t-  newPoints     <- newSTRef []-  offset        <- newSTRef n-  forM_ [0 .. n - 1] $ \i -> forM_ (triangulation V.! i) $ \j ->-    when (i < j) $ when (maxEdgeDistanceSquared i j > maxLen * maxLen) $ do-      p <- readSTRef offset-      writeSTRef offset (p + 1)-      let (p1, p2) = splitEdge i j-      modifySTRef newPoints ((p1, p2) :)-      let [c, d] = (triangulation V.! i) `intersect` (triangulation V.! j)-      deleteEdge t i j-      insertEdge t i p-      insertEdge t j p-      insertEdge t c p-      insertEdge t d p-  -- Add 'newPoints' to mesh points-  -- Convert edges to triangles-  return undefined- where-  splitEdge i j =-    ( lerp 0.5 (meshPointsA mesh V.! i) (meshPointsA mesh V.! j)-    , lerp 0.5 (meshPointsB mesh V.! i) (meshPointsB mesh V.! j)-    )-  maxEdgeDistanceSquared i j =-    distSquared (meshPointsA mesh V.! i) (meshPointsA mesh V.! j)-      `max` distSquared (meshPointsB mesh V.! i) (meshPointsB mesh V.! j)-  n = length (meshPointsA mesh)-  deleteEdge v a b = do-    MV.modify v (delete b) a-    MV.modify v (delete a) b-  insertEdge v a b = do-    MV.modify v (b :) a-    MV.modify v (a :) b-  -- sortEdges :: V.Vector (V2 Double) -> V2 Double -> [Int] -> [Int]-  -- sortEdges v pt = sortOn (\i -> dir pt (v V.! i))--  -- dir :: V2 Double -> V2 Double -> Double-  -- dir a b = (atan2 (crossZ (V2 0 1) (b - a)) (dot (V2 0 1) (b - a)))
src/Reanimate/Math/Triangulate.hs view
@@ -1,18 +1,32 @@+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_HADDOCK hide #-} module Reanimate.Math.Triangulate   ( Triangulation   , edgesToTriangulation   , edgesToTriangulationM   , trianglesToTriangulation   , trianglesToTriangulationM+  , triangulate   ) where +import           Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate')+import           Algorithms.Geometry.PolygonTriangulation.Types+import           Control.Lens import           Control.Monad-import qualified Data.IntSet                   as ISet-import qualified Data.Vector                   as V-import qualified Data.Vector.Mutable           as MV import           Control.Monad.ST-+import           Data.Ext+import           Data.Geometry.PlanarSubdivision                      (PolygonFaceData)+import           Data.Geometry.Point+import           Data.Geometry.Polygon+import qualified Data.IntSet                                          as ISet+import qualified Data.PlaneGraph as Geo+import           Data.Proxy+import qualified Data.Vector                                          as V+import qualified Data.Vector.Mutable                                  as MV+import           Linear.V2+import           Reanimate.Math.Common -- Max edges: n-2 -- Each edge is represented twice: 2n-4 -- Flat structure:@@ -52,3 +66,20 @@     MV.modify v (\x -> a : b : x) c   forM_ [0 .. size - 1] $ \i -> MV.modify v (ISet.toList . ISet.fromList) i   return v+++triangulate :: forall a. (Fractional a, Ord a) => Ring a -> Triangulation+triangulate r = edgesToTriangulation (ringSize r) ds+  where+    ds :: [(Int,Int)]+    ds =+      [ (a^.Geo.vData, b^.Geo.vData)+      | (d, Diagonal) <- V.toList (Geo.edges pg)+      , let (a,b) = Geo.endPointData d pg ]+    pg :: Geo.PlaneGraph () Int PolygonEdgeType PolygonFaceData a+    pg = triangulate' Proxy p+    p :: SimplePolygon Int a+    p = fromPoints $+      [ Point2 x y :+ n+      | (n,V2 x y) <- zip [0..] (V.toList (ringUnpack r)) ]+    -- ringUnpack
src/Reanimate/Math/Visibility.hs view
@@ -20,7 +20,7 @@ --   v is visible: zsv is a left-turn --   v moves in front of the stack: s'sv is right turn --   v moves behind stack:-go :: (Ord a, Fractional a) => V2 a -> [V2 a] -> [V2 a] -> [V2 a]+go :: (Ord a, Fractional a, Epsilon a) => V2 a -> [V2 a] -> [V2 a] -> [V2 a] go _z stack [] = stack go z stack@(s:s':_ss) (v:vs)   | isLeftTurn z s v  = {-trace ("Left: " ++ show (z,s,v)) $ -}go z (v:stack) vs@@ -34,7 +34,7 @@ v: 0,1 -} -rightTurn :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]+rightTurn :: (Ord a, Fractional a, Epsilon a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a] rightTurn z stack' v (v1:vs)   | isRightTurn z v v1 = {-trace ("Double right: " ++ show (v,v1)) $ -}rightTurn z stack v1 vs   | isLeftTurn z v v1 && isRightTurn (head stack') v v1@@ -46,7 +46,7 @@ rightTurn z stack v [] = unwindStack z stack v []  -- scan forwards until edge intersects zv ray-scanc :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]+scanc :: (Ord a, Fractional a, Epsilon a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a] scanc z stack v (v1:v2:vs)   | isBetween u (v1,v2) = -- trace ("Found: " ++ show (u, stack)) $     go z (u:stack) (v2:vs)@@ -56,7 +56,7 @@     Just u = rayIntersect (z,v) (v1,v2) scanc _z stack _v _vs = stack -unwindStack :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> t -> [V2 a]+unwindStack :: (Ord a, Fractional a, Epsilon a) => V2 a -> [V2 a] -> V2 a -> t -> [V2 a] unwindStack z (s1:s2:ss) v vs   | isRightTurn z s1 v && isLeftTurn z s2 v = (u:s2:ss)   | otherwise                               = unwindStack z (s2:ss) v vs@@ -71,7 +71,7 @@ --     unwind stack --  3. Find edge that crosses zv from left to right and is below v -fastForward :: (Ord a, Fractional a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a]+fastForward :: (Ord a, Fractional a, Epsilon a) => V2 a -> [V2 a] -> V2 a -> [V2 a] -> [V2 a] fastForward z stack v (v1:v2:vs)   | isNothing i || not (isBetween u (v1, v2))        = {-trace ("FF past: " ++ show (z,v,v1,v2)) $ -}fastForward z stack v (v2:vs)   | distSquared v u > distSquared z u = {-trace ("FF skip: " ++ show (z,v,v1,v2)) $ -}fastForward z stack v (v2:vs)
src/Reanimate/Memo.hs view
@@ -1,6 +1,23 @@ {-# LANGUAGE BangPatterns              #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RankNTypes                #-}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++With animations defined as SVG images over time, it is unfortunately+quite easy to write inefficient code where expensive expressions are+re-evaluated for every frame even if nothing has changed. This get+around this issue, this module defines a global key-value table that+can store expensive expressions such that they are evaluated only once.++There is currently no way to clear values from the table and it is your+own responsibility to not consume all available memory.++-} module Reanimate.Memo   ( Key(..)   , memo@@ -57,15 +74,20 @@     fn = Just . cacheMapInsert ks v . fromMaybe emptyCacheMap  {-# NOINLINE cacheMap #-}+-- FIXME: There should be a way to clear the cache. cacheMap :: IORef CacheMap cacheMap = unsafePerformIO (newIORef emptyCacheMap) +-- | Keys can either by any boxed object with identity (to be compared with+--   StableNames) or a primitive type with an Eq instance. data Key = forall a. Key !a | forall a. (Typeable a, Eq a, Ord a) => KeyPrim !a  fromKey :: Key -> IO DynamicName fromKey (Key val)     = DynamicName <$> makeStableName val fromKey (KeyPrim val) = pure (DynamicKey val) +-- | Cache expensive value in global store. You must guarantee that the+--   key uniquely determines the value. memo :: Typeable a => [Key] -> a -> a memo !k v = unsafePerformIO $ do   keys <- mapM fromKey k
src/Reanimate/Morph/Common.hs view
@@ -1,6 +1,13 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections   #-} {-# LANGUAGE UnicodeSyntax   #-}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Morph.Common   ( PointCorrespondence   , Trajectory@@ -26,9 +33,9 @@ import           Reanimate.Animation import           Reanimate.ColorComponents import           Reanimate.Ease-import           Reanimate.Math.Polygon    (Polygon, mkPolygon, pAddPoints,-                                            pCentroid, pCutEqual, pSize,-                                            polygonPoints)+import           Reanimate.Math.Polygon    (APolygon, Epsilon, Polygon,+                                            mkPolygon, pAddPoints, pCentroid,+                                            pCutEqual, pSize, polygonPoints) import           Reanimate.PolyShape import           Reanimate.Svg @@ -43,18 +50,35 @@ -- Graphical polygon? FIXME: Come up with a better name. type GPolygon = (DrawAttributes, Polygon) +-- | Method determining how points in the source polygon align with+--   points in the target polygon. type PointCorrespondence = Polygon → Polygon → (Polygon, Polygon)++-- | Method for interpolating between two aligned polygons. type Trajectory = (Polygon, Polygon) → (Double → Polygon)++-- | Method for pairing sets of polygons. type ObjectCorrespondence = [GPolygon] → [GPolygon] → [(GPolygon, GPolygon)] +-- | Morphing strategy data Morph = Morph   { morphTolerance            :: Double+    -- ^ Morphing curves is not always possible and+    --   sometimes shapes are reduced to polygons or meta-curves.+    --   This parameter determined the accuracy of this transformation.   , morphColorComponents      :: ColorComponents+    -- ^ Color components used for color interpolation. LAB is usually+    --   the best option here.   , morphPointCorrespondence  :: PointCorrespondence+    -- ^ Desired point-correspondence algorithm.   , morphTrajectory           :: Trajectory+    -- ^ Desired interpolation algorithm.   , morphObjectCorrespondence :: ObjectCorrespondence+    -- ^ Desired object-correspondence algorithm.   } +{-# INLINE morph #-}+-- | Apply morphing strategy to interpolate between two SVG images. morph :: Morph -> SVG -> SVG -> Double -> SVG morph Morph{..} src dst = \t ->   case t of@@ -77,7 +101,8 @@       , let arranged = morphPointCorrespondence srcPoly' dstPoly'       ] -normalizePolygons :: Polygon -> Polygon -> (Polygon, Polygon)+-- | Add points to each polygon such that they end up with same size.+normalizePolygons :: (Real a, Fractional a, Epsilon a) => APolygon a -> APolygon a -> (APolygon a, APolygon a) normalizePolygons src dst =     (pAddPoints (max 0 $ dstN-srcN) src     ,pAddPoints (max 0 $ srcN-dstN) dst)@@ -99,6 +124,7 @@     interpColor a _ = a     interpOpacity a b = realToFrac (fromToS (realToFrac a) (realToFrac b) t) +-- | Object-correspondence algorithm that spawn objects as necessary. genesisObjectCorrespondence :: ObjectCorrespondence genesisObjectCorrespondence left right =   case (left, right) of@@ -112,6 +138,7 @@   where     emptyFrom a b = mkPolygon $ V.map (const $ pCentroid a) (polygonPoints b) +-- | Object-correspondence algorithm that duplicate objects as necessary. dupObjectCorrespondence :: ObjectCorrespondence dupObjectCorrespondence left right =   case (left, right) of@@ -128,6 +155,8 @@     (x:xs, y:ys) ->       (x, y) : dupObjectCorrespondence xs ys +-- | Object-correspondence algorithm that splits objects in smaller pieces+--   as necessary. splitObjectCorrespondence :: ObjectCorrespondence -- splitObjectCorrespondence = dupObjectCorrespondence splitObjectCorrespondence left right =@@ -168,6 +197,7 @@ -- joinPairs _ _ _ = []  -- FIXME: sort by size, smallest to largest+-- | Extract shapes and their graphical attributes from an SVG node. toShapes :: Double -> SVG -> [(DrawAttributes, Polygon)] toShapes tol src =   [ (attrs, plToPolygon tol shape)@@ -175,9 +205,12 @@   , shape <- map mergePolyShapeHoles $ plGroupShapes $ svgToPolyShapes glyph   ] +-- | Extract the first polygon in an SVG node. Will fail if there+--   are no acceptable shapes. unsafeSVGToPolygon :: Double -> SVG -> Polygon unsafeSVGToPolygon tol src = snd $ head $ toShapes tol src +-- | Map over each polygon in an SVG node. annotatePolygons :: (Polygon -> SVG) -> SVG -> SVG annotatePolygons fn svg = mkGroup   [ fn poly & drawAttributes .~ attr
− src/Reanimate/Morph/LeastWork.hs
@@ -1,449 +0,0 @@-{-# LANGUAGE BangPatterns      #-}-{-# LANGUAGE MultiWayIf        #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-module Reanimate.Morph.LeastWork-  ( StretchCosts(..)-  , defaultStretchCosts-  , zeroStretchCosts-  , stretchWork-  , BendCosts(..)-  , defaultBendCosts-  , bendWork-  , bendInfo-  , leastWork-  , anyLeastWork-  , rawLeastWork-  , leastWork'-  ) where--import           Control.Monad-import           Control.Monad.ST-import           Control.Monad.ST.Unsafe-import           Data.Array.Base-import           Data.Hashable-import           Data.Maybe-import qualified Data.Vector             as V-import qualified Data.Vector.Unboxed     as VU-import           Linear.Metric-import           Linear.V2-import           Linear.Vector-import           Reanimate.Math.Common-import           Reanimate.Math.Polygon-import           Reanimate.Morph.Cache-import           Reanimate.Morph.Common-import           Reanimate.Morph.Linear-import           System.IO--- import           Text.Printf---- import           Debug.Trace--data StretchCosts = StretchCosts-  { stretchStiffness       :: Double-  , stretchCollapsePenalty :: Double-  , stretchElasticity      :: Double-  }--instance Hashable StretchCosts where-  hashWithSalt salt StretchCosts{..} =-    hashWithSalt salt (stretchStiffness, stretchCollapsePenalty, stretchElasticity)--defaultStretchCosts :: StretchCosts-defaultStretchCosts = StretchCosts-  { stretchStiffness       = 1-  , stretchCollapsePenalty = 2-  , stretchElasticity      = 2 }--zeroStretchCosts :: StretchCosts-zeroStretchCosts = defaultStretchCosts{ stretchStiffness = 0 }---- stretch work paramters:---   stretch stiffness---   collapse penalty---   exponent-stretchWork :: StretchCosts -> Double -> Double -> Double-stretchWork _ 0 0 = 0-stretchWork StretchCosts{..} lenStart lenEnd = checkNaN $-    stretchStiffness * (delta**stretchElasticity)/-    ((1-c)*lenMin + c*lenMax)-  where-    checkNaN v-      | isNaN v   = 0-      | otherwise = v-    c = recip stretchCollapsePenalty-    lenMin = min lenStart lenEnd-    lenMax = max lenStart lenEnd-    delta = lenEnd-lenStart---data BendCosts = BendCosts-  { bendStiffness        :: Double-  , bendElasticity       :: Double-  , bendDeviationPenalty :: Double-  , bendZeroPenalty      :: Double-  }--instance Hashable BendCosts where-  hashWithSalt salt BendCosts{..} =-    hashWithSalt salt-      (bendStiffness-      ,bendElasticity-      ,bendDeviationPenalty-      ,bendZeroPenalty)--defaultBendCosts :: BendCosts-defaultBendCosts = BendCosts-  { bendStiffness        = 1-  , bendElasticity       = 2-  , bendDeviationPenalty = 10-  , bendZeroPenalty      = 1000 }--type PType = Double--bendWork :: BendCosts -> V2 PType -> V2 PType -> V2 PType -> V2 PType -> Double-bendWork BendCosts{..} startA endA startB endB =-  case bendInfo startA endA startB endB of-    (deltaAngle, deviation, throughZero) ->-      (bendStiffness * (deltaAngle + bendDeviationPenalty * deviation))**bendElasticity +-      if throughZero then bendZeroPenalty else 0---- deltaX :: Fractional a => V2 a--- deltaX = V2 (recip 1000000000) 0---- testBendInfo :: Polygon -> Polygon -> [Int] -> [Int] -> (Double, Double, Bool)--- testBendInfo a b ~[a1,a2,a3] ~[b1,b2,b3] =---   let startA = realToFrac <$> (pAccess a a1 - pAccess a a2)---       endA   = realToFrac <$> (pAccess b b1 - pAccess b b2)---       endB   = realToFrac <$> (pAccess b b3 - pAccess b b2)---       startB = realToFrac <$> (pAccess a a3 - pAccess a a2)---   in bendInfo startA endA startB endB---- zeroBendFactor :: Fractional a => a--- zeroBendFactor = 1--vZero :: (Ord a, Fractional a) => V2 a -> Bool-vZero (V2 x y) = abs x < epsilon && abs y < epsilon--bendInfo :: V2 PType -> V2 PType -> V2 PType -> V2 PType -> (Double, Double, Bool)--- bendInfo startA endA startB endB---   | vZero startA, not (vZero endA) = bendInfo (endA ^* zeroBendFactor) endA startB endB---   | vZero endA, not (vZero startA) = bendInfo startA (startA ^* zeroBendFactor) startB endB---   | vZero startB, not (vZero endB) = bendInfo startA endA (endB ^* zeroBendFactor) endB---   | vZero endB, not (vZero startB) = bendInfo startA endA startB (startB ^* zeroBendFactor)-bendInfo startA endA startB endB =-    -- trace (show $ map (fmap realToFrac) [startA, endA, startB, endB]) $-    -- trace (show (realToFrac <$> q0, realToFrac <$> q1, realToFrac <$> q2)) $-    -- (ang4, alpha+beta, throughZero)-    (ang4, alpha+beta, throughZero)-  where-    !x0 = dot startA startB-    !y0 = crossZ startA startB-    !x1 = (dot endA startB + dot startA endB) / 2-    !y1 = (crossZ endA startB + crossZ startA endB) / 2-    !x2 = dot endA endB-    !y2 = crossZ endA endB-    fudge x | vZero x = V2 (-0.1) 0-    fudge x = x-    q0,q1, q2 :: V2 PType-    !q0 = fudge $ V2 x0 y0-    !q1 = V2 x1 y1-    !q2 = fudge $ V2 x2 y2-    curve n = q0^*squared (1-n) + q1^*(2*n*(1-n)) + q2^*squared n-    squared a = a*a-    ang3 = abs (angleR q0 q2)-    ang4 = if isWrap then 2*pi - ang3 else ang3-    d0 = crossZ q0 q1-    d1 = crossZ q0 q2 / 2-    d2 = crossZ q1 q2-    roots = filter (>0) $ filter (<1) $ quadraticRoot (d0+d2-2*d1) (2*d1-2*d0) d0-    alpha = fromMaybe 0 $ listToMaybe-      [ min q0Angle q2Angle - ang-      | root <- roots-      , let ang = angleR (V2 1 0) (curve root)-      , ang < min q0Angle q2Angle  ]-    beta = fromMaybe 0 $ listToMaybe-      [ ang - max q0Angle q2Angle-      | root <- roots-      , let ang = angleR (V2 1 0) (curve root)-      , ang > max q0Angle q2Angle  ]-    q0Angle = angleR (V2 1 0) q0-    q2Angle = angleR (V2 1 0) q2-    isWrap = d1*d1 - d0*d2 < 0 && isInside q0 q1 q2 (V2 0 0)-    throughZero = quadThroughZero q0 q1 q2--angleR :: V2 Double -> V2 Double -> Double-angleR a b = atan2 (realToFrac $ crossZ a b) (realToFrac $ dot a b)--leastWork :: StretchCosts -> BendCosts -> PointCorrespondence-leastWork stretchCosts bendCosts =-    cachePointCorrespondence key (leastWork_ stretchCosts bendCosts)-  where-    key = hash ("leastWork v0"::String, stretchCosts, bendCosts)--leastWork_ :: StretchCosts -> BendCosts -> PointCorrespondence-leastWork_ stretchCosts bendCosts src dst-  | pSize src > pSize dst =-    case leastWork stretchCosts bendCosts dst src of-      (dst', src') -> (src', dst')-leastWork_ stretchCosts bendCosts src dst =-    worker undefined (-1) options-  where-    worker bestP _bestPScore [] =-      --trace ("\nBest score: " ++ show _bestPScore)-      bestP-    worker bestP bestPScore (x:xs) =-      let (src',dst', newScore) = leastWork' stretchCosts bendCosts src x in-      if newScore < bestPScore || bestPScore < 0-        then worker (src',dst') newScore xs-        else worker bestP bestPScore xs-    options = pCycles dst-    -- options = [pCycles dst !! 79 ]--anyLeastWork :: StretchCosts -> BendCosts -> PointCorrespondence-anyLeastWork stretchCosts bendCosts src dst =-  case closestLinearCorrespondence src dst of-    (src', dst') -> case leastWork' stretchCosts bendCosts src' dst' of-      (src'', dst'', _score) -> (src'', dst'')--rawLeastWork :: StretchCosts -> BendCosts -> PointCorrespondence-rawLeastWork stretchCosts bendCosts src dst =-  case leastWork' stretchCosts bendCosts src dst of-    (src', dst', _score) -> (src', dst')---- type PointCorrespondence = Polygon → Polygon → (Polygon, Polygon)-leastWork' :: StretchCosts -> BendCosts -> Polygon -> Polygon -> (Polygon, Polygon, Double)-leastWork' stretchCosts bendCosts src dst =-    leastWork'' stretchCosts bendCosts src dst-      srcV dstV (mkDistances srcV) (mkDistances dstV)-  where-    srcV = V.map (fmap realToFrac) $ polygonPoints src-    dstV = V.map (fmap realToFrac) $ polygonPoints dst-    mkDistances poly = VU.generate (length poly) $ \i ->-      distance (V.unsafeIndex poly i) (V.unsafeIndex poly ((i+1) `mod` length poly))--leastWork'' :: StretchCosts -> BendCosts -> Polygon -> Polygon-  -> V.Vector (V2 Double) -> V.Vector (V2 Double)-  -> VU.Vector Double -> VU.Vector Double-  -> (Polygon, Polygon, Double)-leastWork'' stretchCosts bendCosts src dst srcV dstV srcDist dstDist = runST $ do-    -- unsafeIOToST $ hPutStrLn stderr $ "Size: " ++ show (nSrc, nDst)-    -- unsafeIOToST $ hFlush stderr-    work <- asArray $ newArray ((0,0),(nSrc,nDst)) (0::Double)-    north <- asArray $ newArray ((0,0),(nSrc,nDst)) (0::Int)-    west <- asArray $ newArray ((0,0),(nSrc,nDst)) (0::Int)-    let -- calc :: Int -> Int -> ST s ()-        calc = \i j -> do-          let cond = False -- i==14 && j==26-          w0 <- if i==0 then return 0 else do-            prevWork <- readArray work (i-1,j)-            prevWest <- readArray west (i-1,j)-            prevNorth <- readArray north (i-1,j)-            let !len = VU.unsafeIndex srcDist (i-1)-                -- !len = distance (realToFrac <$> pAccess src (i-1)) (realToFrac <$> pAccess src i)-                !sWork = stretchWork stretchCosts len 0-                -- !srcPrev   = realToFrac <$> pAccess src (i-1-prevWest)-                !srcPrev   = V.unsafeIndex srcV (i-1-prevWest)-                -- !srcMiddle = realToFrac <$> pAccess src (i-1)-                !srcMiddle   = V.unsafeIndex srcV (i-1)-                -- !srcNext   = realToFrac <$> pAccess src i-                !srcNext   = V.unsafeIndex srcV i-                -- !dstPrev   = realToFrac <$> pAccess dst (j-prevNorth)-                !dstPrev   = V.unsafeIndex dstV (j-prevNorth)-                -- !dstMiddle = realToFrac <$> pAccess dst j-                !dstMiddle = V.unsafeIndex dstV j-                !bWork =-                  bendWork bendCosts-                    (srcPrev-srcMiddle) (dstPrev-dstMiddle)-                    (srcNext-srcMiddle) (V2 0 0)-            -- when (len' /= len) $ do-            --   error $ "w0: Bad length: " ++ show (len', len, i, j)-            when cond $ do-              unsafeIOToST $ hPutStrLn stderr $ "West:  " ++ show (i-1-prevWest, i-1, i)-              unsafeIOToST $ hPutStrLn stderr $ "West:  " ++ show (j-prevNorth, j, j)-              unsafeIOToST $ hPutStrLn stderr $ "bWork: " ++ show (bWork)-              unsafeIOToST $ hPutStrLn stderr $ "len:    " ++ show (len)-              unsafeIOToST $ hPutStrLn stderr $ "sWork:  " ++ show (sWork)-            return (prevWork + sWork + bWork + if prevWest==0&&prevNorth==1 then 10000 else 0)-          w1 <- if j==0 then return 0 else do-            prevWork <- readArray work (i,j-1)-            prevWest <- readArray west (i,j-1)-            prevNorth <- readArray north (i,j-1)-            let len = VU.unsafeIndex dstDist (j-1)-                --len = distance (realToFrac <$> pAccess dst (j-1)) (realToFrac <$> pAccess dst j)-                sWork = stretchWork stretchCosts 0 len-                -- srcPrev   = realToFrac <$> pAccess src (i-prevWest)-                -- srcMiddle = realToFrac <$> pAccess src i-                -- dstPrev   = realToFrac <$> pAccess dst (j-1-prevNorth)-                -- dstMiddle = realToFrac <$> pAccess dst (j-1)-                -- dstNext   = realToFrac <$> pAccess dst j-                srcPrev   = V.unsafeIndex srcV (i-prevWest)-                srcMiddle = V.unsafeIndex srcV i-                dstPrev   = V.unsafeIndex dstV (j-1-prevNorth)-                dstMiddle = V.unsafeIndex dstV (j-1)-                dstNext   = V.unsafeIndex dstV j-                bWork =-                  bendWork bendCosts-                      (srcPrev-srcMiddle) (dstPrev-dstMiddle)-                      (V2 0 0) (dstNext-dstMiddle)-            -- when (len' /= len) $ do-            --   error $ "w1: Bad length: " ++ show (len', len, j, realToFrac <$> pAccess dst (j-1),  realToFrac <$> pAccess dst j)-            when (isNaN bWork) $-              error $ "bWork NaN: " ++ show (i,j)-            when (isNaN sWork) $-              error $ "sWork NaN: " ++ show (i,j, len)-            when cond $ do-              unsafeIOToST $ hPutStrLn stderr $ "North:  " ++ show (i-prevWest, i, i)-              unsafeIOToST $ hPutStrLn stderr $ "North:  " ++ show (j-1-prevNorth, j-1, j)-              unsafeIOToST $ hPutStrLn stderr $ "bWork:  " ++ show (bWork)-              unsafeIOToST $ hPutStrLn stderr $ "len:    " ++ show (len)-              unsafeIOToST $ hPutStrLn stderr $ "sWork:  " ++ show (sWork)-            return (prevWork + sWork + bWork + if prevWest==1&&prevNorth==0 then 10000 else 0)-          w2 <- if j==0 || i==0 then return 0 else do-            prevWork <- readArray work (i-1,j-1)-            prevWest <- readArray west (i-1,j-1)-            prevNorth <- readArray north (i-1,j-1)-            -- let findPrev key = do-            --       keyNorth <- readArray north-            let -- startLen = distance (realToFrac <$> pAccess src (i-1)) (realToFrac <$> pAccess src i)-                -- endLen = distance (realToFrac <$> pAccess dst (j-1)) (realToFrac <$> pAccess dst j)-                startLen = VU.unsafeIndex srcDist (i-1)-                endLen = VU.unsafeIndex dstDist (j-1)-                sWork = stretchWork stretchCosts startLen endLen-                -- srcPrev   = realToFrac <$> pAccess src (i-1-prevWest)-                -- srcMiddle = realToFrac <$> pAccess src (i-1)-                -- srcNext   = realToFrac <$> pAccess src i-                -- dstPrev   = realToFrac <$> pAccess dst (j-1-prevNorth)-                -- dstMiddle = realToFrac <$> pAccess dst (j-1)-                -- dstNext   = realToFrac <$> pAccess dst j-                srcPrev   = V.unsafeIndex srcV (i-1-prevWest)-                srcMiddle = V.unsafeIndex srcV (i-1)-                srcNext   = V.unsafeIndex srcV i-                dstPrev   = V.unsafeIndex dstV (j-1-prevNorth)-                -- dstPrev'   = V.unsafeIndex dstV (j-2)-                dstMiddle = V.unsafeIndex dstV (j-1)-                dstNext   = V.unsafeIndex dstV j-                bWork =-                  bendWork bendCosts-                    (srcPrev-srcMiddle) (dstPrev-dstMiddle)-                    (srcNext-srcMiddle) (dstNext-dstMiddle)-                -- bWork' =-                --   bendWork bendCosts-                --     (srcPrev-srcMiddle) (dstPrev'-dstMiddle)-                --     (srcNext-srcMiddle) (dstNext-dstMiddle)-            when cond $ do-              unsafeIOToST $ hPutStrLn stderr $ "Diag:  " ++ show (i-1-prevWest, i-1, i)-              unsafeIOToST $ hPutStrLn stderr $ "Diag:  " ++ show (j-1-prevNorth, j-1, j)-              unsafeIOToST $ hPutStrLn stderr $ "bWork: " ++ show (bWork)-              -- unsafeIOToST $ hPutStrLn stderr $ "bWork': " ++ show (bWork')-            return (prevWork + sWork + bWork)-          let goNorth = do-                when cond $ do-                  unsafeIOToST $ hPutStrLn stderr "Go north"-                  unsafeIOToST $ hPrint stderr (w0, w1, w2)-                writeArray work (i,j) w1-                writeArray north (i,j) 1-              goWest = do-                when cond $ do-                  unsafeIOToST $ hPutStrLn stderr "Go west"-                  unsafeIOToST $ hPrint stderr (w0, w1, w2)-                writeArray work (i,j) w0-                writeArray west (i,j) 1-              goNorthWest = do-                when cond $-                  unsafeIOToST $ hPutStrLn stderr "Go northwest"-                writeArray work (i,j) w2-                writeArray north (i,j) 1-                writeArray west (i,j) 1-          -- when (j==2 && i==2) $-          --   unsafeIOToST $ hPrint stderr (i,j,w0, w1, w2)-          when (isNaN w0 || isNaN w1 || isNaN w2) $-            error $ "NaN: " ++ show (w0,w1,w2, i, j)-          if | i==0                 -> goNorth-             | j==0                 -> goWest-             | w2 <= w0 && w2 <= w1 -> goNorthWest-             | w0 <= w1 && w0 <= w2 -> goWest-             | w1 <= w0 && w1 <= w2 -> goNorth-             | otherwise            -> error $ "urk: " ++ show (w0, w1, w2, i, j)-    forM_ [1.. max nSrc nDst-1] $ \idx -> do-      forM_ [0..idx-1] $ \i -> do-        when (idx < nSrc && i < nDst) $-          calc idx i-        when (i < nSrc && idx < nDst) $-          calc i idx-      when (idx < nSrc && idx < nDst) $-        calc idx idx-    -- workMatrix <- unsafeFreeze work-    -- unsafeIOToST $ do-    --   hPutStr stderr "   "-    --   forM_ [0..nSrc-1] $ \i -> do-    --     hPutStr stderr (printf "%6d " i)-    --   hPutStr stderr "\n"-    --   forM_ [0..nDst-1] $ \j -> do-    --     hPutStr stderr (printf "%2d " j)-    --     forM_ [0..nSrc-1] $ \i -> do-    --       hPutStr stderr (printf "%6.1f " (workMatrix ! (i,j)))-    --     hPutStr stderr "\n"-    finalWork <- readArray work (nSrc-1,nDst-1)-    let walk = \i j acc -> do-          -- w <- readArray work (i,j)-          -- when (i==14) $-          --   unsafeIOToST $ hPrint stderr (i,j)-          let acc' = (i,j):acc-          if (i==0 && j==0)-            then return acc'-            else do-              isNorth <- (==1) <$> readArray north (i,j)-              isWest  <- (==1) <$> readArray west (i,j)-              if | isNorth && isWest -> walk (i-1) (j-1) acc'-                 | isNorth           -> walk i (j-1) acc'-                 | otherwise         -> walk (i-1) j acc'-    pairs <- walk (nSrc-1) (nDst-1) []-    return-      ( mkPolygon $ V.fromList [ pAccess src idx | idx <- map fst pairs ]-      , mkPolygon $ V.fromList [ pAccess dst idx | idx <- map snd pairs ]-      , finalWork-      )-  where-    nSrc = pSize src-    nDst = pSize dst-    asArray :: ST s (STUArray s i e) -> ST s (STUArray s i e)-    asArray = id--{--quadToCubic (QuadBezier a b c) =-  CubicBezier a ((1/3)*^(a ^+^ 2*^b)) ((1/3)*^(2*^b ^+^ c)) c--cubicRoot (p3 - 3*p2 + 3*p1 - p0) (3*p2 - 6*p1 + 3*p0) (3*p1 - 3*p0) p0-  where (CubicBezier (Point p0 _) (Point p1 _) (Point p2 _) (Point p3 _)) = b--p3 = a-p2 = ((1/3)*^(a ^+^ 2*^b))-p1 = ((1/3)*^(2*^b ^+^ c))-p0 = c-cubicRoot 0 (a - 2b + c) (2b - 2c) c--}--quadThroughZero :: V2 Double -> V2 Double -> V2 Double -> Bool-quadThroughZero a@(V2 _ q0) b@(V2 _ q1) c@(V2 _ q2) =-    any (xPositive . eval) $ filter (\x -> x > 0 && x < 1) extrema-  where-    xPositive (V2 x _) = x>0-    eval n = a^*squared (1-n) + b^*(2*n*(1-n)) + c^*squared n-    squared x = x*x-    extrema = quadraticRoot (q2-2*q1+q0) (2*q1-2*q0) q0--quadraticRoot :: Double -> Double -> Double -> [Double]-quadraticRoot a b c-  | a == 0 && b == 0 = []-  | a == 0 = [-c/b]-  | otherwise = result-  where-    d = b*b - 4*a*c-    bSign | b < 0 = -1-          | otherwise = 1-    q = - (b + bSign * sqrt d) / 2-    x1 = q/a-    x2 = c/q-    result | d < 0     = []-           | d == 0    = [x1]-           | otherwise = [x1, x2]
− src/Reanimate/Morph/LineBend.hs
@@ -1,108 +0,0 @@-module Reanimate.Morph.LineBend-  ( lineBend-  , lineBendRaw-  ) where--import qualified Data.Vector            as V-import           Linear.Matrix          (det22)-import           Linear.Metric-import           Linear.V2-import           Linear.Vector-import           Reanimate.Math.Common-import           Reanimate.Math.Polygon-import           Reanimate.Morph.Common--lineBend :: Trajectory-lineBend = lineBend' True--lineBendRaw :: Trajectory-lineBendRaw = lineBend' False---- Note: Returns polygon with n+1 elements.-lineBend' :: Bool -> Trajectory-lineBend' corrected (a,b) = \t ->-    let u = 1 - t-        -- phi = V.zipWith (\l r -> u*l + t*r) phi_a phi_b-        phi = V.zipWith (\l r -> lerpAngle l r t) phi_a phi_b-        -- alpha_zero = u * alpha_a_zero + t * alpha_b_zero-        alpha_zero = lerpAngle alpha_a_zero alpha_b_zero t-        alpha = V.scanl (+) alpha_zero phi-        bigE =-          V.sum $ V.zipWith (\diff alp -> squared diff * squared (cos alp)) lengths_diff alpha-        bigF =-          V.sum $ V.zipWith (\diff alp -> squared diff * sin alp * cos alp) lengths_diff alpha-        bigG =-          V.sum $ V.zipWith (\diff alp -> squared diff * squared (sin alp)) lengths_diff alpha-        bigU =-          2 * V.sum-          (V.zipWith3 (\len_a len_b alp -> (u*len_a + t*len_b) * cos alp) lengths_a lengths_b alpha)-        bigV =-          2 * V.sum-          (V.zipWith3 (\len_a len_b alp -> (u*len_a + t*len_b) * sin alp) lengths_a lengths_b alpha)-        lam1 = det22 (V2 (V2 bigU bigF) (V2 bigV bigG)) /-               det22 (V2 (V2 bigE bigF) (V2 bigF bigG))-        lam2 = det22 (V2 (V2 bigE bigU) (V2 bigF bigV)) /-               det22 (V2 (V2 bigE bigF) (V2 bigF bigG))-        s_vect = V.fromList-          [ -0.5 * squared (lengths_diff V.! n) *-            (lam1 * cos (alpha V.! n) +-             lam2 * sin (alpha V.! n))-          | n <- [0 .. pSize a-1]]-        lengths = V.zipWith3 (\l r s -> u*l + t*r + if corrected then s else 0) lengths_a lengths_b s_vect-        movingCenter :: V2 Double-        movingCenter = lerp t (realToFrac <$> pCentroid b) (realToFrac <$> pCentroid a)-        centerAng :: Double-        centerAng = lerpAngle centoid_angle_a centoid_angle_b t-        centerLen = u*centoid_len_a + t*centoid_len_b-        -- V2 x_zero y_zero = lerp t (realToFrac <$> b V.! 0) (realToFrac <$> a V.! 0)-        V2 x_zero y_zero = movingCenter + V2 (cos centerAng * centerLen) (sin centerAng * centerLen)-        x_modifiers = V.zipWith (*) lengths (V.map cos alpha)-        y_modifiers = V.zipWith (*) lengths (V.map sin alpha)-        xs = V.scanl (+) x_zero x_modifiers-        ys = V.scanl (+) y_zero y_modifiers-    in mkPolygon $ V.map (fmap realToFrac) $ V.zipWith V2 xs ys-  where-    squared x = x*x-    centoid_angle_a :: Double-    centoid_angle_a = lineAngle (pCentroid a + V2 1 0) (pCentroid a) (pAccess a 0)-    centoid_angle_b = lineAngle (pCentroid b + V2 1 0) (pCentroid b) (pAccess b 0)-    centoid_len_a :: Double-    centoid_len_a = realToFrac $ approxDist (pCentroid a) (pAccess a 0)-    centoid_len_b = realToFrac $ approxDist (pCentroid b) (pAccess b 0)-    alpha_a_zero = lineAngle (pAccess a 0 + V2 1 0) (pAccess a 0) (pAccess a 1)-    alpha_b_zero = lineAngle (pAccess b 0 + V2 1 0) (pAccess b 0) (pAccess b 1)-    lengths_a = computeLength a-    lengths_b = computeLength b-    lengths_diff' = V.zipWith (\l r -> abs (l-r)) lengths_a lengths_b-    lengths_tol = 0.0001 * V.maximum lengths_diff'-    lengths_diff = V.map (max lengths_tol) lengths_diff'-    phi_a = computePhi a-    phi_b = computePhi b--    computeLength :: Polygon -> V.Vector Double-    computeLength poly = V.fromList-      [ realToFrac $ approxDist this next-      | n <- [0 .. pSize poly-1]-      , let this = pAccess poly n-            next = pAccess poly (pNext a n)-      ]-    computePhi poly = V.fromList $-      [ negate $ angle' (next-this) ((prev-this) ^* (-1))-      | n <- [1 .. pSize poly-1]-      , let prev = pAccess poly (n-1)-            this = pAccess poly n-            next = pAccess poly (pNext a n)-      ]--lerpAngle :: Double -> Double -> (Double -> Double)-lerpAngle fromAng toAng t-  | abs (fromAng - (toAng+2*pi)) < abs (fromAng - toAng) = (1-t)*fromAng + t*(toAng+2*pi)-  | abs (fromAng - (toAng-2*pi)) < abs (fromAng - toAng) = (1-t)*fromAng + t*(toAng-2*pi)-  | otherwise = (1-t)*fromAng + t*toAng---- Angle from a through b to c. Measured on the right-hand side, from 0 to tau-lineAngle :: V2 Rational -> V2 Rational -> V2 Rational -> Double-lineAngle a b c = angle' (a-b) (c-b)--angle' :: V2 Rational -> V2 Rational -> Double-angle' a b = atan2 (realToFrac $ crossZ a b) (realToFrac $ dot a b)
src/Reanimate/Morph/Linear.hs view
@@ -1,7 +1,14 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Morph.Linear   ( linear, rawLinear-  , linearCorrespondence   , closestLinearCorrespondence+  , closestLinearCorrespondenceA   , linearTrajectory   ) where @@ -14,25 +21,53 @@ import           Reanimate.Morph.Cache import           Reanimate.Morph.Common +-- | Linear interpolation strategy.+--+--   Example:+--+--   > playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+--   >   withStrokeLineJoin JoinRound $+--   >   let src = scale 8 $ center $ latex "X"+--   >       dst = scale 8 $ center $ latex "H"+--   >   in morph linear src dst t+--+--   <<docs/gifs/doc_linear.gif>> linear :: Morph linear = rawLinear   { morphPointCorrespondence  =       cachePointCorrespondence (hash ("closest"::String))         closestLinearCorrespondence } +-- | Linear interpolation strategy without realigning corners.+--   May give better results if the polygons are already aligned.+--   Usually gives worse results.+--+--   Example:+--+--   > playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+--   >   withStrokeLineJoin JoinRound $+--   >   let src = scale 8 $ center $ latex "X"+--   >       dst = scale 8 $ center $ latex "H"+--   >   in morph rawLinear src dst t+--+--   <<docs/gifs/doc_rawLinear.gif>> rawLinear :: Morph rawLinear = Morph   { morphTolerance            = 0.001   , morphColorComponents      = labComponents-  , morphPointCorrespondence  = linearCorrespondence+  , morphPointCorrespondence  = normalizePolygons   , morphTrajectory           = linearTrajectory   , morphObjectCorrespondence = splitObjectCorrespondence } -linearCorrespondence :: PointCorrespondence-linearCorrespondence = normalizePolygons-+-- | Cycle polygons until the sum of the point trajectory path lengths+--   is smallest. closestLinearCorrespondence :: PointCorrespondence-closestLinearCorrespondence src' dst' =+closestLinearCorrespondence = closestLinearCorrespondenceA++-- | Cycle polygons until the sum of the point trajectory path lengths+--   is smallest.+closestLinearCorrespondenceA :: (Real a, Fractional a, Epsilon a) => APolygon a -> APolygon a -> (APolygon a, APolygon a)+closestLinearCorrespondenceA src' dst' =     (src, worker dst (score dst) options)   where     (src, dst) = normalizePolygons src' dst'@@ -44,9 +79,11 @@         else worker bestP bestPScore xs     options = pCycles dst     score p = sum-      [ approxDist (pAccess src n) (pAccess p n)+      [ -- approxDist (pAccess src n) (pAccess p n)+        distSquared (pAccess src n) (pAccess p n)       | n <- [0 .. pSize src-1] ] +-- | Strategy for moving points in a linear (straight-line) trajectory. linearTrajectory :: Trajectory linearTrajectory (src,dst)   | pSize src == pSize dst = \t -> mkPolygon $
− src/Reanimate/Morph/Rigid.hs
@@ -1,281 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{--The shape of a triangle can be represented by two explicit points (ie vectors)-and a third implicit point at (0,0). A triangle defined by three points can be-converted by subtracting one of the points from the others:-  shape (p1,p2,p3) = (p2-p1, p3-p1)-This keeps the shape intact but drops the position of the triangle.--If P and Q are two such triangles, there exists a matrix A that transforms-P into Q: AP = Q-The 'A' matrix can be calculated by multiplying Q and the inverse of P:-  A = QP_inv--So, if we have a set of triangles, we can drop their positional information-and compute their shape transformation matrices. But can we do it in reverse?-Can we start with a set of transformation matrices (which don't give us the-positions of the triangles) and compute a set of points that represent triangles-with the right transformation?-In other words, if we have a set of triangles (with absolute position) and-a transformation matrix for each triangle, can we compute new absolute positions-such that AP=Q?--Solving this is not straightforward as moving a single point will change the-transformation of multiple triangles. Fortunately, though, this problem can be-expression as linear equations and solved quickly with LAPACK.--To express the problem as a linear system, we need 4 equations for each-triangle and 2 unknowns for each point.--The system has this shape: Mx=B-We're trying to find 'x' which are the positions of the new points.-'M' is a matrix that transforms points into transformation matrices.-'B' is the target transformation matrices.--With a bit of rewriting, we can reduce 'A = QP_inv' to 4 linear equations-with 6 unknowns. The 6 unknowns are the x and y positions for the three corners-in a triangle.-This is just another way of calculating the transformation matrix 'A' and we-can verify it by looking at the output of:-  * computeA src dst-  * applyCoeff (coeffOfB src) dst-The output should be the same because they're both computing 'A'.--'applyCoeff' simply multiplies the coefficients by a 6x1 matrix containing-the x and y positions for 'dst'.--So, this means we have roughly the right shape: Mx=B. M is the coefficients-we found for 'src', 'x' is dst, and 'B' is the transformation matrix that-turns 'src' into 'dst'.-We can get the coefficients for each triangle and place them together in a-single large matrix. Then we can put the desired transformation matrices in 'B'-and ask a linear solver to find 'x' which satisfies the equations.--Phew, step one is done. We now have the machinery for turning arbitrary-transformation matrices into proper, connected triangles. Next step is figuring-out which rotational matrices to use. At the two extremes, we have the identity-matrices (representing our starting shape) and the matrices computed from the-target triangles (represetning out target shape). How can these two matrices be-interpolated for a smooth morph?--There are several ways of interpolating matrices. Linear interpolation is-possible. But, it looks prettier if we separate rotation from shear. Then we-can use spherical linear interpolation on the rotation and linear interpolation-on the shear.---}-module Reanimate.Morph.Rigid where--import           Data.Foldable (toList)-import           Data.Vector                   (Vector)-import qualified Data.Vector                   as V-import           Linear.Quaternion-import           Linear.Vector-import           Linear.V2-import           Linear.V3-import qualified Numeric.LinearAlgebra         as Matrix-import           Numeric.LinearAlgebra.HMatrix (GMatrix, Matrix,-                                                toLists, (!), (><))-import Reanimate.Animation-import Reanimate.Svg---type P = V2 Double-type Trig = (P,P,P)-type RelTrig = (Int,Int,Int)--data Mesh = Mesh-  { meshPointsA :: Vector P-  , meshPointsB :: Vector P-  , meshOutline :: Vector Int-  -- , meshSteiner :: Vector Int-  , meshTriangles :: Vector RelTrig }--renderMeshPair :: Mesh -> SVG-renderMeshPair Mesh{..} = withStrokeColor "black" $ mkGroup-  [ mkLinePathClosed-    [ (aPx, aPy)-    , (bPx, bPy)-    , (cPx, cPy)]-  | (a,b,c) <- V.toList meshTriangles-  , let V2 aPx aPy = meshPointsA V.! a-        V2 bPx bPy = meshPointsA V.! b-        V2 cPx cPy = meshPointsA V.! c-  ]---- applyA (computeA a b) a = b + some_constant_translation--- A = Q P_inv-computeA :: Trig -> Trig -> Matrix Double-computeA p q = matQ <> Matrix.inv matP-  where-    matP = trigToMatrix p-    matQ = trigToMatrix q---- A = UDV = U(VV_t)DV = (UV)(V_tDV) = RS--- R = UV--- S = V_tDV-computeRS :: Matrix Double -> (Matrix Double, Matrix Double)-computeRS a = (r, s)-  where-    (u,d,vt) = Matrix.svd a-    v = Matrix.tr vt-    r = u <> v-    s = vt <> Matrix.diag d <> v--matrixToQuaternion :: Matrix Double -> Quaternion Double-matrixToQuaternion r =  q-  where-    w = 0.5 * sqrt (1 + r!0!0 + r!1!1 + 1)-    z = 1/(4*w) * (r!0!1 - r!1!0)-    q = Quaternion w (V3 0 0 z)--quaternionToMatrix :: Quaternion Double -> Matrix Double-quaternionToMatrix q = (2><2)-    [ 1 - 2*(qj*qj + qk*qk), 2*(qi*qj+qk*qr)-    , 2*(qi*qj - qk*qr), 1 - 2*(qi*qi + qk*qk) ]-  where-    [qr,qi,qj,qk] = toList q---- A = R((1-t)I + tS)-computeA_RSt :: Matrix Double -> Matrix Double -> Double -> Matrix Double-computeA_RSt r s t = r_t <> (realToFrac (1-t) * Matrix.ident 2 + realToFrac t * s)-  where-    i = Quaternion 1 0-    q = slerp i (matrixToQuaternion r) t-    r_t = quaternionToMatrix q--applyA :: Matrix Double -> Trig -> Trig-applyA a p =-    case toLists (a <> matP) of-      [ [x1, x2], [y1, y2] ] -> (V2 0 0, V2 x1 y1, V2 x2 y2)-      _ -> error "invalid matrix"-  where-    matP = trigToMatrix p--coeffOfB :: Trig -> Matrix Double-coeffOfB p = (4><6)-    [ -a0-a2,      0, a0,   0, a2,  0-    , -a1-a3,      0, a1,   0, a3,  0-    ,      0, -a0-a2,  0,  a0,  0, a2-    ,      0, -a1-a3,  0,  a1,  0, a3 ]-  where-    [[a0,a1],[a2,a3]] = toLists (Matrix.inv (trigToMatrix p))--applyCoeff :: Matrix Double -> Trig -> Matrix Double-applyCoeff b (V2 x1 y1, V2 x2 y2, V2 x3 y3) = b <> matQ-  where-    matQ = (6><1) [ x1, y1, x2, y2, x3, y3]--trigToMatrix :: Trig -> Matrix Double-trigToMatrix (p1,p2,p3) = matP-  where-    V2 p12x p12y = p2-p1-    V2 p13x p13y = p3-p1-    matP = (2><2)-      [p12x, p13x-      ,p12y, p13y]----data Prep = Prep-  { prepPivot    :: (P, P)-  , prepPointsA  :: Vector P-  , prepPointsB  :: Vector P-  , prepRS       :: Vector (Matrix Double, Matrix Double)-  , prepRSRev    :: Vector (Matrix Double, Matrix Double)-  , prepUToB     :: GMatrix-  }--symmetric :: Bool-symmetric = True--prepare :: Mesh -> Prep-prepare Mesh{..} = Prep-    { prepPivot = (aOrigin, bOrigin)-    , prepPointsA =-        V.map (subtract aOrigin) $-        V.take pivotIdx meshPointsA <> V.drop (pivotIdx+1) meshPointsA-    , prepPointsB =-        V.map (subtract bOrigin) $-        V.take pivotIdx meshPointsB <> V.drop (pivotIdx+1) meshPointsB-    , prepRS = rsList-    , prepRSRev = rsRevList-    , prepUToB = Matrix.mkSparse uToB }-  where-    aOrigin = meshPointsA V.! pivotIdx-    bOrigin = meshPointsB V.! pivotIdx-    pivotIdx = 0 -    -- pivotIdx = case V.head meshTriangles of-    --     (a,_,_) -> a-    mkAbs p (a,b,c) = (p V.! a,p V.! b,p V.! c)-    absATrigs = V.map (mkAbs meshPointsA) meshTriangles-    absBTrigs = V.map (mkAbs meshPointsB) meshTriangles-    aList = V.zipWith computeA absATrigs absBTrigs-    rsList = V.map computeRS aList-    revList = V.zipWith computeA absBTrigs absATrigs-    rsRevList = V.map computeRS revList-    n = length meshTriangles-    -- nT = if symmetric then n*2 else n-    -- pivotToB = ((nT*4)><2) $ concat-    --   [ [ fromMaybe 0 (lookup (x,pivotIdx*2) bigM)-    --     , fromMaybe 0 (lookup (x,pivotIdx*2+1) bigM)]-    --   | x <- [0..(nT*4)-1] ]-    uToB =-      [ ((x,y-2),key) | ((x,y),key) <- bigM, y /= pivotIdx*2 && y /= pivotIdx*2+1 ]-    bigM =-      concat (zipWith worker [0..] (V.toList meshTriangles)) ++-      if symmetric-        then concat $ zipWith workerRev [n..] (V.toList meshTriangles)-        else []-    worker i src@(a,b,c) = concat $-      let effs = coeffOfB (mkAbs meshPointsA src) in-      [ [((i*4+h, e*2), effs!h!(j*2))-        ,((i*4+h, e*2+1), effs!h!(j*2+1))]-      | h <- [0..3]-      , (e,j) <- zip [a,b,c] [0..]-      ]-    workerRev i dst@(a,b,c) = concat $-      let effs = coeffOfB (mkAbs meshPointsB dst) in-      [ [((i*4+h, e*2), effs!h!(j*2))-        ,((i*4+h, e*2+1), effs!h!(j*2+1))]-      | h <- [0..3]-      , (e,j) <- zip [a,b,c] [0..]-      ]--interpolate :: Prep -> Double -> Vector P-interpolate Prep{..} t = V.fromList $-    pivot : worker (Matrix.toList solution)-  where-    -- solution = Matrix.cgSolve False prepUToB b-    solution = Matrix.cgx $ last solutions-    solutions = Matrix.cgSolve'-      False-      1e-9-      1e-9-      1000-      prepUToB-      b-      (Matrix.fromList $ concat [ [x,y] | V2 x y <- V.toList target ])-      -- 0-    target = if t < 0.5-      then prepPointsA-      else prepPointsB-    worker (x:y:xs) = V2 x y ^+^ pivot : worker xs-    worker _ = []-    pivot = case prepPivot of-      (src, dst) -> lerp t dst src-    n = V.length prepRS-    b = Matrix.vector $-      [ concat (toLists a)!!j-      | i <- [0..n-1]-      , let (r,s) = prepRS V.! i-      , let a = computeA_RSt r s t-      , j <- [0..3]-      ] ++-      [ concat (toLists a)!!j-      | symmetric-      , i <- [0..n-1]-      , let (r,s) = prepRSRev V.! i-      , let a = computeA_RSt r s (1-t)-      , j <- [0..3]-      ]
src/Reanimate/Morph/Rotational.hs view
@@ -1,5 +1,13 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Morph.Rotational-  ( rotationalTrajectory+  ( Origin+  , rotationalTrajectory   , polygonOrigin   ) where @@ -12,8 +20,22 @@ import           Reanimate.Morph.Common import           Reanimate.Math.Polygon +-- | Rotational origin relative to polygon center.+--   (0.5, 0.5) is center of polygon. Top right is (1,1) and+--   bottom left is (0,0) type Origin = (Double, Double) +-- | Interpolation by rotating around an origin point.+--+--   Example:+--+--   > playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->+--   >   withStrokeLineJoin JoinRound $+--   >   let src = scale 8 $ center $ latex "X"+--   >       dst = scale 8 $ center $ latex "H"+--   >   in morph linear{morphTrajectory=rotationalTrajectory (0.5,0.5)} src dst t+--+--   <<docs/gifs/doc_rotationalTrajectory.gif>> rotationalTrajectory :: Origin -> Trajectory rotationalTrajectory origin (src,dst) =     \t ->@@ -34,6 +56,7 @@      originAngle o = lineAngle (o + V2 1 0) o +-- | Compute the absolute position of rotational origin point in polygon. polygonOrigin :: Polygon -> Origin -> V2 Double polygonOrigin poly (originX, originY) =   case pBoundingBox poly of
src/Reanimate/Parameters.hs view
@@ -1,3 +1,7 @@+{- |+  Parameters define the global context of an animation. They are set once+  before an animation is rendered and may not change during rendering.+-} module Reanimate.Parameters   ( Raster(..)   , Width@@ -20,16 +24,20 @@ import System.IO.Unsafe import Data.IORef +-- | Width of animation in pixels. type Width = Int+-- | Height of animation in pixels. type Height = Int+-- | Framerate of animation in frames per second. type FPS = Int +-- | Raster engines turn SVG images into pixels. data Raster-  = RasterNone-  | RasterAuto-  | RasterInkscape-  | RasterRSvg-  | RasterMagick+  = RasterNone     -- ^ Do not use any external raster engine. Rely on the browser or ffmpeg.+  | RasterAuto     -- ^ Scan for installed raster engines and pick the fastest one.+  | RasterInkscape -- ^ Use Inkscape to raster SVG images.+  | RasterRSvg     -- ^ Use rsvg-convert to raster SVG images.+  | RasterMagick   -- ^ Use imagemagick to raster SVG images.   deriving (Show, Eq)  {-# NOINLINE pRasterRef #-}@@ -37,9 +45,11 @@ pRasterRef = unsafePerformIO (newIORef RasterNone)  {-# NOINLINE pRaster #-}+-- | Selected raster engine. pRaster :: Raster pRaster = unsafePerformIO (readIORef pRasterRef) +-- | Set raster engine. setRaster :: Raster -> IO () setRaster = writeIORef pRasterRef @@ -48,9 +58,11 @@ pFPSRef = unsafePerformIO (newIORef 0)  {-# NOINLINE pFPS #-}+-- | Selected framerate. pFPS :: FPS pFPS = unsafePerformIO (readIORef pFPSRef) +-- | Set desired framerate. setFPS :: FPS -> IO () setFPS = writeIORef pFPSRef @@ -59,21 +71,24 @@ pWidthRef = unsafePerformIO (newIORef 0)  {-# NOINLINE pWidth #-}+-- | Width of animation in pixel. pWidth :: Width pWidth = unsafePerformIO (readIORef pWidthRef) +-- | Set desired width of animation in pixel. setWidth :: Width -> IO () setWidth = writeIORef pWidthRef - {-# NOINLINE pHeightRef #-} pHeightRef :: IORef FPS pHeightRef = unsafePerformIO (newIORef 0)  {-# NOINLINE pHeight #-}+-- | Height of animation in pixel. pHeight :: Height pHeight = unsafePerformIO (readIORef pHeightRef) +-- | Set desired height of animation in pixel. setHeight :: Height -> IO () setHeight = writeIORef pHeightRef @@ -82,9 +97,13 @@ pNoExternalsRef = unsafePerformIO (newIORef False)  {-# NOINLINE pNoExternals #-}+-- | This parameter determined whether or not external tools are allowed.+--   If this flag is True then tools such as 'Reanimate.LaTeX.latex' and+--   'Reanimate.Blender.blender' will not be invoked. pNoExternals :: Bool pNoExternals = unsafePerformIO (readIORef pNoExternalsRef) +-- | Set whether external tools are allowed. setNoExternals :: Bool -> IO () setNoExternals = writeIORef pNoExternalsRef @@ -93,8 +112,11 @@ pRootDirectoryRef = unsafePerformIO (newIORef (error "root directory not set"))  {-# NOINLINE pRootDirectory #-}+-- | Root directory of animation. Images and other data has to be placed+--   here if they are referenced in an SVG image. pRootDirectory :: FilePath pRootDirectory = unsafePerformIO (readIORef pRootDirectoryRef) +-- | Set the root animation directory. setRootDirectory :: FilePath -> IO () setRootDirectory = writeIORef pRootDirectoryRef
src/Reanimate/PolyShape.hs view
@@ -1,6 +1,17 @@+{-|+Module      : Reanimate.PolyShape+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++A PolyShape is a closed set of curves.++-} module Reanimate.PolyShape   ( PolyShape(..)-  , PolyShapeWithHoles(..)+  , PolyShapeWithHoles   , svgToPolyShapes     -- :: Tree -> [PolyShape]   , svgToPolygons       -- :: Double -> Svg -> [Polygon] @@ -18,7 +29,6 @@    , plFromPolygon       -- :: [RPoint] -> PolyShape   , plToPolygon         -- :: Double -> PolyShape -> Polygon-  , plPolygonify        -- :: Double -> PolyShape -> [Point Double]   , plDecompose         -- :: [PolyShape] -> [[RPoint]]   , unionPolyShapes     -- :: [PolyShape] -> [PolyShape]   , unionPolyShapes'    -- :: Double -> [PolyShape] -> [PolyShape]@@ -26,89 +36,89 @@   , decomposePolygon    -- :: [Point Double] -> [[RPoint]]   , plGroupShapes       -- :: [PolyShape] -> [PolyShapeWithHoles]   , mergePolyShapeHoles -- :: PolyShapeWithHoles -> PolyShape-  , polyShapeTolerance-  , plPartial, plPartialGroup, plPartial'-  , splitPolyShape      -- :: Double -> Int -> PolyShape -> [PolyShape]+  , plPartial   , plGroupTouching   ) where -import           Chiphunk.Low-import           Control.Lens           ((&), (.~))-import           Data.AdditiveGroup-import           Data.List              (minimumBy, nub, partition, sortOn)-import           Data.Ord-import qualified Data.Vector            as V-import           Geom2D                 (rotate90L, rotate90R, ($*), (^*))-import           Geom2D.CubicBezier     (ClosedPath (..), CubicBezier (..),-                                         DPoint, FillRule (..), PathJoin (..),-                                         Point (..), QuadBezier (..), arcLength,-                                         arcLengthParam, bezierIntersection,-                                         bezierSubsegment, closedPathCurves,-                                         closest, colinear, curvesToClosed,-                                         evalBezier, interpolateVector,-                                         quadToCubic, reorient, splitBezier,-                                         union, vectorDistance)-import           Graphics.SvgTree       (PathCommand (..), RPoint, Tree (..),-                                         defaultSvg, pathDefinition)+import           Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate')+import           Control.Lens ((&), (.~), (^.))+import           Data.Ext+import           Data.Geometry.PlanarSubdivision (PolygonFaceData (..))+import qualified Data.Geometry.Point as Geo+import qualified Data.Geometry.Polygon as Geo+import           Data.List (nub, partition, sortOn)+import qualified Data.PlaneGraph as Geo+import           Data.Proxy+import qualified Data.Vector as V+import           Geom2D.CubicBezier.Linear (ClosedPath (..), CubicBezier (..), FillRule (..),+                                            PathJoin (..), QuadBezier (..), arcLength,+                                            arcLengthParam, bezierIntersection, bezierSubsegment,+                                            closedPathCurves, closest, colinear, curvesToClosed,+                                            evalBezier, quadToCubic, reorient, splitBezier, union,+                                            vectorDistance)+import           Graphics.SvgTree (PathCommand (..), RPoint, Tree (..), defaultSvg, pathDefinition) import           Linear.V2 import           Reanimate.Animation import           Reanimate.Constants-import           Reanimate.Math.EarClip-import           Reanimate.Math.Polygon (Polygon, mkPolygon, pIsCCW, pRing,-                                         pdualPolygons, polygonPoints)-import           Reanimate.Math.SSSP+import           Reanimate.Math.Polygon (Polygon, mkPolygon, pArea, pIsCCW) import           Reanimate.Svg  -- | Shape drawn by continuous line. May have overlap, may be convex. newtype PolyShape = PolyShape { unPolyShape :: ClosedPath Double }   deriving (Show) +-- | Polyshape with smaller, fully-enclosed holes. data PolyShapeWithHoles = PolyShapeWithHoles   { polyShapeParent :: PolyShape   , polyShapeHoles  :: [PolyShape]   }  +-- | Render a set of polyshapes as a single SVG path. renderPolyShapes :: [PolyShape] -> Tree renderPolyShapes pls =   PathTree $ defaultSvg & pathDefinition .~ concatMap plPathCommands pls +-- | Render a polyshape as a single SVG path. renderPolyShape :: PolyShape -> Tree renderPolyShape pl =     PathTree $ defaultSvg & pathDefinition .~ plPathCommands pl +-- | Render control-points of a polyshape as circles. renderPolyShapePoints :: PolyShape -> Tree renderPolyShapePoints = mkGroup . map renderPoint . plCurves   where-    renderPoint (CubicBezier (Point x y) _ _ _) =+    renderPoint (CubicBezier (V2 x y) _ _ _) =       translate x y $ mkCircle 0.02 +-- | Length of polyshape circumference. plLength :: PolyShape -> Double plLength = sum . map cubicLength . plCurves   where     cubicLength c = arcLength c 1 polyShapeTolerance +-- | Area of polyshape. plArea :: PolyShape -> Double-plArea pl = areaForPoly (map toVect $ plPolygonify polyShapeTolerance pl) 0-  where-    toVect (Point x y) = Vect x y+plArea pl = realToFrac $ pArea $ plToPolygon polyShapeTolerance pl  -- 1/10th of a pixel if rendered at 2560x1440 polyShapeTolerance :: Double polyShapeTolerance = screenWidth/25600 +-- | Construct a polyshape from the vertices in a polygon. plFromPolygon :: [RPoint] -> PolyShape plFromPolygon = PolyShape . ClosedPath . map worker   where-    worker (V2 x y) = (Point x y, JoinLine)+    worker val = (val, JoinLine) +-- | Approximate a polyshape as a polygon within the given tolerance. plToPolygon :: Double -> PolyShape -> Polygon plToPolygon tol pl =-  let p = V.init . V.fromList . map (\(Point x y) -> realToFrac <$> V2 x y) .+  let p = V.init . V.fromList . map (fmap realToFrac) .           plPolygonify tol $ pl   in if pIsCCW (mkPolygon p) then mkPolygon p else mkPolygon (V.reverse p) -+-- | Partially draw polyshape. plPartial :: Double -> PolyShape -> PolyShape plPartial delta pl | delta >= 1 = pl plPartial delta pl = PolyShape $ curvesToClosed (lineOut ++ [joinB] ++ lineIn)@@ -133,47 +143,41 @@ -- toPDual :: Polygon -> Dual -> PDual -- pdualReduce :: Polygon -> PDual -> Int -> PDual -- pdualPolygons :: Polygon -> PDual -> [Polygon]-splitPolyShape :: Double -> Int -> PolyShape -> [PolyShape]-splitPolyShape tol n poly =-    let polygon = toPolygon (plPolygonify tol poly)-        trig = earClip $ pRing polygon-        d = dual 0 trig-        pd = toPDual (pRing polygon) d-        reduced = pdualReduce (pRing polygon) pd n-        polygons = pdualPolygons polygon reduced-    in map toPolyShape polygons-  where-    toPolygon :: [Point Double] -> Polygon-    toPolygon = mkPolygon . V.fromList . nub . map (\(Point x y) -> V2 (realToFrac x) (realToFrac y))-    toPolyShape :: Polygon -> PolyShape-    toPolyShape = plFromPolygon . map (fmap realToFrac) . V.toList . polygonPoints--plPartialGroup :: Double -> [PolyShape] -> [PolyShape]-plPartialGroup _delta [] = []-plPartialGroup delta pls =-    [ plPartial (delta*(maxLen/plLength pl)) pl | pl <- pls ]-  where-    maxLen = maximum $ map plLength pls+-- splitPolyShape :: Double -> Int -> PolyShape -> [PolyShape]+-- splitPolyShape tol n poly =+--     let polygon = toPolygon (plPolygonify tol poly)+--         trig = triangulate $ pRing polygon+--         d = dual 0 trig+--         pd = toPDual (pRing polygon) d+--         reduced = pdualReduce (pRing polygon) pd n+--         polygons = pdualPolygons polygon reduced+--     in map toPolyShape polygons+--   where+--     toPolygon :: [RPoint] -> Polygon+--     toPolygon = mkPolygon . V.fromList . nub . map (fmap realToFrac)+--     toPolyShape :: Polygon -> PolyShape+--     toPolyShape = plFromPolygon . map (fmap realToFrac) . V.toList . polygonPoints -plPartial' :: Double -> ([DPoint], PolyShape) -> PolyShape-plPartial' delta (seen', PolyShape (ClosedPath lst)) =-  case lst of-    []                         -> PolyShape (ClosedPath [])-    (startP, startJoin) : rest -> PolyShape $ ClosedPath $-      (startP, startJoin) : worker startP rest-  where-    seen = filter (`elem` plPoints) seen'-    closestSeen pt = minimumBy (comparing (vectorDistance pt)) seen-    worker _ [] = []-    worker _ ((newP, newJoin) : rest)-      | newP `elem` seen = (newP, newJoin) : worker newP rest-      | otherwise =-        let newAt = interpolateVector (closestSeen newP) newP delta-        in (newAt, newJoin) : worker newAt rest-    plPoints =-      [ p | (p,_) <- lst ]+-- plPartial' :: Double -> ([RPoint], PolyShape) -> PolyShape+-- plPartial' delta (seen', PolyShape (ClosedPath lst)) =+--   case lst of+--     []                         -> PolyShape (ClosedPath [])+--     (startP, startJoin) : rest -> PolyShape $ ClosedPath $+--       (startP, startJoin) : worker startP rest+--   where+--     seen = filter (`elem` plPoints) seen'+--     closestSeen pt = minimumBy (comparing (vectorDistance pt)) seen+--     worker _ [] = []+--     worker _ ((newP, newJoin) : rest)+--       | newP `elem` seen = (newP, newJoin) : worker newP rest+--       | otherwise =+--         let newAt = interpolateVector (closestSeen newP) newP delta+--         in (newAt, newJoin) : worker newAt rest+--     plPoints =+--       [ p | (p,_) <- lst ] -plGroupTouching :: [PolyShape] -> [[([DPoint],PolyShape)]]+-- | Find intersection points.+plGroupTouching :: [PolyShape] -> [[([RPoint],PolyShape)]] plGroupTouching [] = [] plGroupTouching pls = worker [polyShapeOrigin (head pls)] pls   where@@ -191,7 +195,7 @@         helper acc lst@((startP,startJ):rest)           | startP `elem` seen = lst ++ reverse acc           | otherwise = helper ((startP, startJ):acc) rest-    plPoints :: PolyShape -> [Point Double]+    plPoints :: PolyShape -> [RPoint]     plPoints (PolyShape (ClosedPath lst)) =       [ p | (p,_) <- lst ] @@ -206,17 +210,21 @@   plGroupShapes .   unionPolyShapes -decomposePolygon :: [Point Double] -> [[RPoint]]+-- | Split polygon into smaller, convex polygons.+decomposePolygon :: [RPoint] -> [[RPoint]] decomposePolygon poly =-  map (map fromVect . adjust) $ convexDecomposition (map toVect poly) tol+  [ [ V2 x y+    | v <- V.toList (Geo.boundaryVertices f pg)+    , let Geo.Point2 x y =(pg^.Geo.vertexDataOf v) ^. Geo.location ]+  | (f, Inside) <- V.toList (Geo.internalFaces pg) ]+   where-    tol = polyShapeTolerance-    toVect (Point x y) = Vect x y-    fromVect (Vect x y) = V2 x y-    adjust [] = []-    adjust x  = if head x == last x then adjust (init x) else x+    pg = triangulate' Proxy p+    p = Geo.fromPoints $+      [ Geo.Point2 x y :+ ()+      | V2 x y <- poly ] -plPolygonify :: Double -> PolyShape -> [Point Double]+plPolygonify :: Double -> PolyShape -> [RPoint] plPolygonify tol shape =     startPoint (head curves) : concatMap worker curves   where@@ -232,38 +240,41 @@     endPoint (CubicBezier _ _ _ d) = d     startPoint (CubicBezier a _ _ _) = a -+-- | Convert a polyshape to a list of SVG path commands. plPathCommands :: PolyShape -> [PathCommand] plPathCommands = lineToPath . plLineCommands +-- | Convert a polyshape to a list of line commands. plLineCommands :: PolyShape -> [LineCommand] plLineCommands pl =   case curves of     []                  -> []     (CubicBezier start _ _ _:_) ->-      LineMove (toRPoint start) :+      LineMove start :       zipWith worker (drop 1 dstList ++ [start]) joinList ++-      [LineEnd (toRPoint start)]+      [LineEnd start]   where     ClosedPath closedPath = unPolyShape pl     (dstList, joinList) = unzip closedPath     curves = plCurves pl     worker dst JoinLine =-      LineBezier [toRPoint dst]+      LineBezier [dst]     worker dst (JoinCurve a b) =-      LineBezier $ map toRPoint [a,b,dst]-    toRPoint :: Point Double -> RPoint-    toRPoint (Point x y) = V2 x y+      LineBezier [a,b,dst] +-- | Extract all shapes from SVG nodes. Drawing attributes such+--   as stroke and fill color are discarded. svgToPolyShapes :: Tree -> [PolyShape] svgToPolyShapes = cmdsToPolyShapes . toLineCommands . extractPath +-- | Extract all polygons from SVG nodes. Curves are approximated to+--   within the given tolerance. svgToPolygons :: Double -> SVG -> [Polygon] svgToPolygons tol = map (toPolygon . plPolygonify tol) . svgToPolyShapes   where-    toPolygon :: [Point Double] -> Polygon+    toPolygon :: [RPoint] -> Polygon     toPolygon = mkPolygon .-      V.fromList . nub . map (\(Point x y) -> V2 (realToFrac x) (realToFrac y))+      V.fromList . nub . map (fmap realToFrac)  cmdsToPolyShapes :: [LineCommand] -> [PolyShape] cmdsToPolyShapes [] = []@@ -280,44 +291,41 @@       finalize acc $       worker newStart [] xs     worker from acc (LineEnd orig:LineMove dst:xs) | from /= orig =-      finalize ((toGPoint from, JoinLine):acc) $+      finalize ((from, JoinLine):acc) $       worker dst [] xs     worker _from acc (LineEnd{}:LineMove dst:xs) =       finalize acc $       worker dst [] xs     worker from acc [LineEnd orig] | from /= orig =-      finalize ((toGPoint from, JoinLine):acc) []+      finalize ((from, JoinLine):acc) []     worker _from acc [LineEnd{}] =       finalize acc []     worker from acc (LineBezier [x]:xs) =-      worker x ((toGPoint from, JoinLine) : acc) xs+      worker x ((from, JoinLine) : acc) xs     worker from acc (LineBezier [a,b]:xs) =-      let quad = QuadBezier (toGPoint from) (toGPoint a) (toGPoint b)+      let quad = QuadBezier from a b           CubicBezier _ a' b' c' = quadToCubic quad-      in worker from acc (LineBezier (map fromGPoint [a',b',c']):xs)+      in worker from acc (LineBezier [a',b',c']:xs)     worker from acc (LineBezier [a,b,c]:xs) =-      worker c ((toGPoint from, JoinCurve (toGPoint a) (toGPoint b)) : acc) xs+      worker c ((from, JoinCurve a b) : acc) xs     worker _ _ _ = bad -    toGPoint :: RPoint -> Point Double-    toGPoint (V2 x y) = Point x y-    fromGPoint :: Point Double -> RPoint-    fromGPoint (Point x y) = V2 x y-+-- | Merge overlapping shapes. unionPolyShapes :: [PolyShape] -> [PolyShape] unionPolyShapes shapes =     map PolyShape $-    union (map unPolyShape shapes) NonZero (polyShapeTolerance/10000)+    union (map unPolyShape shapes) FillNonZero (polyShapeTolerance/10000) +-- | Merge overlapping shapes to within given tolerance. unionPolyShapes' :: Double -> [PolyShape] -> [PolyShape] unionPolyShapes' tol shapes =     map PolyShape $-    union (map unPolyShape shapes) NonZero tol+    union (map unPolyShape shapes) FillNonZero tol --- True iff lhs is inside of rhs.--- lhs and rhs may not overlap.--- Implementation: Trace a vertical line through the origin of A and check--- of this line intersects and odd number of times on both sides of A.+-- | True iff lhs is inside of rhs.+--   lhs and rhs may not overlap.+--   Implementation: Trace a vertical line through the origin of A and check+--   of this line intersects and odd number of times on both sides of A. isInsideOf :: PolyShape -> PolyShape -> Bool lhs `isInsideOf` rhs =     odd (length upHits) && odd (length downHits)@@ -325,7 +333,7 @@     (upHits, downHits) = polyIntersections origin rhs     origin = polyShapeOrigin lhs -polyIntersections :: DPoint -> PolyShape -> ([DPoint],[DPoint])+polyIntersections :: RPoint -> PolyShape -> ([RPoint],[RPoint]) polyIntersections origin rhs =     (nub $ concatMap (intersections rayUp) curves     ,nub $ concatMap (intersections rayDown) curves)@@ -335,15 +343,16 @@     intersections line bs =       map (evalBezier bs . fst) (bezierIntersection bs line polyShapeTolerance)     limit = 1000-    rayUp = CubicBezier origin origin origin (Point limit limit)-    rayDown = CubicBezier origin origin origin (Point (-limit) (-limit))+    rayUp = CubicBezier origin origin origin (V2 limit limit)+    rayDown = CubicBezier origin origin origin (V2 (-limit) (-limit)) -polyShapeOrigin :: PolyShape -> Point Double+polyShapeOrigin :: PolyShape -> V2 Double polyShapeOrigin (PolyShape closedPath) =   case closedPath of-    ClosedPath []            -> Point 0 0+    ClosedPath []            -> V2 0 0     ClosedPath ((start,_):_) -> start +-- | Find holes and group them with their parent. plGroupShapes :: [PolyShape] -> [PolyShapeWithHoles] plGroupShapes = worker   where@@ -364,6 +373,7 @@ instance Eq PolyShape where   a == b = plCurves a == plCurves b +-- | Cut out holes. mergePolyShapeHoles :: PolyShapeWithHoles -> PolyShape mergePolyShapeHoles (PolyShapeWithHoles parent []) = parent mergePolyShapeHoles (PolyShapeWithHoles parent (child:children)) =@@ -413,12 +423,12 @@       [x2p]     )   where-    vect = (childOrigin ^-^ p) ^* 0 -- 0.0001-    vectL = rotate90L $* vect-    vectR = rotate90R $* vect+    -- vect = (childOrigin - p) * 0 -- 0.0001+    vectL = 0 -- rotate90L $* vect+    vectR = 0 -- rotate90R $* vect     score = vectorDistance childOrigin p     childOrigin = polyShapeOrigin child-    childOrigin' = childOrigin ^-^ vectL+    childOrigin' = childOrigin - vectL     (pHead:pTail) = plCurves parent     childCurves = plCurves child @@ -426,16 +436,17 @@      (a2p, p2b') = splitBezier pHead pParam     p2b = case p2b' of-      CubicBezier a b c d -> CubicBezier (a ^-^ vectL) b c d+      CubicBezier a b c d -> CubicBezier (a - vectL) b c d      p = evalBezier pHead pParam     -- straight line to child origin-    p2x = lineBetween (p ^-^ vectR) childOrigin+    p2x = lineBetween (p - vectR) childOrigin     -- straight line from child origin     x2p = lineBetween childOrigin' p      lineBetween a = CubicBezier a a a +-- | Destruct a polyshape into constituent curves. plCurves :: PolyShape -> [CubicBezier Double] plCurves = closedPathCurves . unPolyShape 
src/Reanimate/Povray.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE OverloadedStrings #-}+{-|+  [Povray](http://povray.org/) is a scriptable raytracer. All povray functions+  are cached and will reuse images when scripts stay the same.+-} module Reanimate.Povray   ( povray   , povrayQuick@@ -10,20 +14,19 @@   , povrayExtreme'   ) where -import           Codec.Picture.Png-import qualified Data.ByteString            as B+import           Data.Hashable import           Data.Text                  (Text) import qualified Data.Text                  as T import qualified Data.Text.IO               as T import           Graphics.SvgTree           (Tree (..)) import           Reanimate.Cache+import           Reanimate.Constants import           Reanimate.Misc-import           Reanimate.Raster import           Reanimate.Parameters+import           Reanimate.Raster import           Reanimate.Svg.Constructors import           System.FilePath            (replaceExtension, (<.>)) import           System.IO.Unsafe           (unsafePerformIO)-import           Data.Hashable  povrayRaw :: [String] -> Text -> Tree povrayRaw args script =@@ -33,39 +36,58 @@ povrayRaw' args script =   unsafePerformIO $ mkPovrayImage' args script +-- | Run the povray raytracer with a default resolution of 320x180+--   and antialiasing enabled. The resulting image is scaled to fit+--   the screen exactly. povray :: [String] -> Text -> Tree povray args = povrayRaw (["+H180","+W320", "+A"] ++ args) +-- | Run the povray raytracer with a default resolution of 320x180+--   and antialiasing enabled. The FilePath points to a PNG file+--   containing the resulting image. povray' :: [String] -> Text -> FilePath povray' args = povrayRaw' (["+H180","+W320", "+A"] ++ args) +-- | Run the povray raytracer with a default resolution of 320x180+--   but without antialiasing. The resulting image is scaled to fit+--   the screen exactly. povrayQuick :: [String] -> Text -> Tree povrayQuick args = povrayRaw (["+H180","+W320"] ++ args) +-- | Run the povray raytracer with a default resolution of 320x180+--   but without antialiasing. The FilePath points to a PNG file+--   containing the resulting image. povrayQuick' :: [String] -> Text -> FilePath povrayQuick' args = povrayRaw' (["+H180","+W320"] ++ args) +-- | Run the povray raytracer with a default resolution of 1440x2560+--   and antialiasing enabled. The FilePath points to a PNG file+--   containing the resulting image. povraySlow :: [String] -> Text -> Tree povraySlow args = povrayRaw (["+H1440","+W2560", "+A"] ++ args) +-- | Run the povray raytracer with a default resolution of 1440x2560+--   and antialiasing enabled. The FilePath points to a PNG file+--   containing the resulting image. povraySlow' :: [String] -> Text -> FilePath povraySlow' args = povrayRaw' (["+H1440","+W2560", "+A"] ++ args) +-- | Run the povray raytracer with a default resolution of 2160x3840+--   and antialiasing enabled. The FilePath points to a PNG file+--   containing the resulting image. povrayExtreme :: [String] -> Text -> Tree povrayExtreme args = povrayRaw (["+H2160","+W3840", "+A"] ++ args) +-- | Run the povray raytracer with a default resolution of 2160x3840+--   and antialiasing enabled. The FilePath points to a PNG file+--   containing the resulting image. povrayExtreme' :: [String] -> Text -> FilePath povrayExtreme' args = povrayRaw' (["+H2160","+W3840", "+A"] ++ args)  mkPovrayImage :: [String] -> Text -> IO Tree mkPovrayImage _ script | pNoExternals = pure $ mkText script-mkPovrayImage args script = do-    out <- mkPovrayImage' args script-    -- return $ center $ scaleToSize 16 9 $ embedImageFile out-    png <- B.readFile out-    case decodePng png of-      Left{}    -> error "bad image"-      Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img+mkPovrayImage args script =+  mkImage screenWidth screenHeight <$> mkPovrayImage' args script  mkPovrayImage' :: [String] -> Text -> IO FilePath mkPovrayImage' _ _ | pNoExternals = pure "/povray/has/been/disabled"@@ -75,5 +97,5 @@     T.writeFile pov_file script     runCmd exec (args ++ ["-D","+UA", pov_file, "+o"++target])   where-    template = show (hash key) <.> "png"+    template = encodeInt (hash key) <.> "png"     key = T.concat (script:map T.pack args)
src/Reanimate/Raster.hs view
@@ -1,3 +1,14 @@+{-|+Module      : Reanimate.Raster+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Tools for generating, manipulating, and embedding raster images.++-} module Reanimate.Raster   ( mkImage           -- :: Double -> Double -> FilePath -> SVG   , cacheImage        -- :: (PngSavable pixel, Hashable a) => a -> Image pixel -> FilePath@@ -119,6 +130,8 @@   target   = pRootDirectory </> encodeInt hashPath <.> takeExtension path   hashPath = hash path +-- | Write in-memory image to cache file if (and only if) such cache file doesn't+--   already exist. cacheImage :: (PngSavable pixel, Hashable a) => a -> Image pixel -> FilePath cacheImage key gen = unsafePerformIO $ cacheFile template $ \path ->   writePng path gen@@ -126,6 +139,8 @@  -- Warning: Caching svg elements with links to external objects does --          not work. 2020-06-01+-- | Same as 'prerenderSvg' but returns the location of the rendered image+--   as a FilePath. prerenderSvgFile :: Hashable a => a -> Width -> Height -> SVG -> FilePath prerenderSvgFile key width height svg =   unsafePerformIO $ cacheFile template $ \path -> do@@ -139,6 +154,11 @@                        (Just $ Px $ fromIntegral height)                        svg +-- | Render SVG node to a PNG file and return a new node containing+--   that image. For static SVG nodes, this can hugely improve performance.+--   The first argument is the key that determines SVG uniqueness. It+--   is entirely your responsibility to ensure that all keys are unique.+--   If they are not, you will be served stale results from the cache. prerenderSvg :: Hashable a => a -> SVG -> SVG prerenderSvg key =   mkImage screenWidth screenHeight . prerenderSvgFile key pWidth pHeight@@ -232,17 +252,17 @@     Left{}    -> error "bad image"     Right img -> return img --- | Use 'potrace' to trace edges in a raster image and convert them to SVG polygons.+-- | Use \'potrace\' to trace edges in a raster image and convert them to SVG polygons. vectorize :: FilePath -> SVG vectorize = vectorize_ [] --- | Same as 'vectorize' but takes a list of arguments for 'potrace'.+-- | Same as 'vectorize' but takes a list of arguments for \'potrace\'. vectorize_ :: [String] -> FilePath -> SVG vectorize_ _ path | pNoExternals = mkText $ T.pack path vectorize_ args path             = unsafePerformIO $ do   root <- getXdgDirectory XdgCache "reanimate"   createDirectoryIfMissing True root-  let svgPath = root </> show key <.> "svg"+  let svgPath = root </> encodeInt key <.> "svg"   hit <- doesFileExist svgPath   unless hit $ withSystemTempFile "file.svg" $ \tmpSvgPath svgH ->     withSystemTempFile "file.bmp" $ \tmpBmpPath bmpH -> do@@ -289,7 +309,7 @@     engine <- requireRaster pRaster     applyRaster engine svgPath  where-  template = show (hash rendered) <.> "png"+  template = encodeInt (hash rendered) <.> "png"   rendered = renderSvg (Just $ Px $ fromIntegral width)                        (Just $ Px $ fromIntegral height)                        svg
src/Reanimate/Render.hs view
@@ -1,8 +1,20 @@ {-# LANGUAGE MultiWayIf #-}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX++Internal tools for rastering SVGs and rendering videos. You are unlikely+to ever directly use the functions in this module.++-} module Reanimate.Render   ( render-  , renderSvgs            -- :: Animation -> IO ()+  , renderSvgs   , renderSnippets        -- :: Animation -> IO ()+  , renderOneFrame   , Format(..)   , Raster(..)   , Width, Height, FPS@@ -13,13 +25,13 @@  import           Control.Concurrent import           Control.Exception-import           Control.Monad          (forM_, forever, unless, void, when)+import           Control.Monad             (forM_, forever, unless, void, when) import           Data.Either import           Data.Function-import qualified Data.Text              as T-import qualified Data.Text.IO           as T+import qualified Data.Text                 as T+import qualified Data.Text.IO              as T import           Data.Time-import           Graphics.SvgTree       (Number (..))+import           Graphics.SvgTree          (Number (..)) import           Numeric import           Reanimate.Animation import           Reanimate.Driver.Check@@ -28,24 +40,40 @@ import           Reanimate.Parameters import           System.Console.ANSI.Codes import           System.Exit-import           System.FilePath        ((</>), replaceExtension)+import           System.FileLock (withTryFileLock, SharedExclusive(..), unlockFile)+import           System.Directory+import           System.FilePath           (replaceExtension, (<.>), (</>)) import           System.IO-import           Text.Printf            (printf)+import           Text.Printf               (printf) -renderSvgs :: Animation -> IO ()-renderSvgs ani = do+idempotentFile :: FilePath -> IO () -> IO ()+idempotentFile path action = do+    _ <- withTryFileLock lockFile Exclusive $ \lock -> do+      haveFile <- doesFileExist path+      unless haveFile action+      unlockFile lock+      _ <- try (removeFile lockFile) :: IO (Either SomeException ())+      return ()+    return ()+  where+    lockFile = path <.> "lock"++-- | Generate SVGs at 60fps and put them in a folder.+renderSvgs :: FilePath -> Int -> Bool -> Animation -> IO ()+renderSvgs folder offset _prettyPrint ani = do   print frameCount   lock <- newMVar () -  handle errHandler $ concurrentForM_ (frameOrder rate frameCount) $ \nth -> do-    let -- frame = frameAt (recip (fromIntegral rate-1) * fromIntegral nth) ani+  handle errHandler $ concurrentForM_ (frameOrder rate frameCount) $ \nth' -> do+    let nth = (nth'+offset) `mod` frameCount         now = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth         frame = frameAt (if frameCount <= 1 then 0 else now) ani         svg = renderSvg Nothing Nothing frame-    _ <- evaluate (length svg)+        path = folder </> show nth <.> "svg"++    idempotentFile path $ writeFile path svg     withMVar lock $ \_ -> do-      putStr (show nth)-      T.putStrLn $ T.concat . T.lines . T.pack $ svg+      print nth       hFlush stdout  where   rate       = 60@@ -54,7 +82,33 @@     hPutStrLn stderr msg     exitWith (ExitFailure 1) +-- | Select a single frame that doesn't already exist in the output+--   folder and render it. If all frames have been rendered, print "Done".+renderOneFrame :: FilePath -> Int -> Bool -> Int -> Animation -> IO ()+renderOneFrame folder offset _prettyPrint rate ani =+    worker (frameOrder rate frameCount)+  where+    worker [] = putStrLn "Done"+    worker (x:xs) = do+      let nth = (x+offset) `mod` frameCount+          now = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth+          frame = frameAt (if frameCount <= 1 then 0 else now) ani+          svg = renderSvg Nothing Nothing frame+          path = folder </> show nth <.> "svg"+          tmpPath = path <.> "tmp"+      haveFile <- doesFileExist path+      if haveFile+        then worker xs+        else do+          writeFile tmpPath svg+          renameOrCopyFile tmpPath path+          print nth+    frameCount = round (duration ani * fromIntegral rate) :: Int+ -- XXX: Merge with 'renderSvgs'+-- | Render 10 frames and print them to stdout. Used for testing.+--+--   XXX: Not related to the snippets in the playground. renderSnippets :: Animation -> IO () renderSnippets ani = forM_ [0 .. frameCount - 1] $ \nth -> do   let now   = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth@@ -76,6 +130,7 @@                                                [0, nthFrame .. nFrames - 1]   where isSeen x = any (\y -> x `mod` y == 0) seen +-- | Video formats supported by reanimate. data Format = RenderMp4 | RenderGif | RenderWebm   deriving (Show) @@ -106,6 +161,7 @@ -- gifArguments :: FPS -> FilePath -> FilePath -> FilePath -> [String] -- gifArguments fps progress template target = +-- | Render animation to a video file with given parameters. render   :: Animation   -> FilePath@@ -256,7 +312,7 @@  where   isValidRaster RasterNone = False   isValidRaster RasterAuto = False-  isValidRaster _ = True+  isValidRaster _          = True    width  = Just $ Px $ fromIntegral width_   height = Just $ Px $ fromIntegral height_@@ -290,6 +346,8 @@ rasterTemplate RasterAuto = "render-%05d.svg" rasterTemplate _          = "render-%05d.png" +-- | Resolve RasterNone and RasterAuto. If no valid raster can+--   be found, exit with an error message. requireRaster :: Raster -> IO Raster requireRaster raster = do   raster' <- selectRaster (if raster == RasterNone then RasterAuto else raster)@@ -302,6 +360,8 @@       exitWith (ExitFailure 1)     _ -> pure raster' +-- | Resolve RasterNone and RasterAuto. If no valid raster can+--   be found, return RasterNone. selectRaster :: Raster -> IO Raster selectRaster RasterAuto = do   rsvg   <- hasRSvg@@ -314,6 +374,8 @@     | otherwise      -> pure RasterNone selectRaster r = pure r +-- | Convert SVG file to a PNG file with selected raster engine. If+--   raster engine is RasterAuto or RasterNone, do nothing. applyRaster :: Raster -> FilePath -> IO () applyRaster RasterNone     _    = return () applyRaster RasterAuto     _    = return ()
src/Reanimate/Scene.hs view
@@ -1,11 +1,9 @@ {-# LANGUAGE ApplicativeDo             #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RankNTypes                #-}-{-# LANGUAGE TemplateHaskell           #-} {-# LANGUAGE RecordWildCards           #-} {-| Module      : Reanimate.Scene-Description : Imperative animation API Copyright   : Written by David Himmelstrup License     : Unlicense Maintainer  : lemmih@gmail.com@@ -19,6 +17,7 @@   ( -- * Scenes     Scene   , ZIndex+  , scene             -- :: (forall s. Scene s a) -> Animation   , sceneAnimation    -- :: (forall s. Scene s a) -> Animation   , play              -- :: Animation -> Scene s ()   , fork              -- :: Scene s a -> Scene s a@@ -61,9 +60,19 @@   , spriteScope       -- :: Scene s a -> Scene s a    -- * Object API-  , Renderable(..)   , Object   , ObjectData+  , oNew+  , newObject+  , oModify+  , oModifyS+  , oRead+  , oTween+  , oTweenS+  , oTweenV+  , oTweenVS+  , Renderable(..)+  -- ** Object Properties   , oTranslate   , oSVG   , oContext@@ -88,15 +97,7 @@   , oLeftX   , oRightX   , oCenterXY-  , newObject   , oValue-  , oModify-  , oModifyS-  , oRead-  , oTween-  , oTweenS-  , oTweenV-  , oTweenVS    -- ** Graphics object methods   , oShow@@ -127,7 +128,6 @@    -- * ST internals   , liftST-  , asAnimation       -- :: (forall s. Scene s a) -> Scene s Animation   , transitionO   , evalScene   )@@ -159,6 +159,8 @@ -- [(Time, Animation, ZIndex)] -- Map Time [(Animation, ZIndex)] type Gen s = ST s (Duration -> Time -> (SVG, ZIndex))+-- | A 'Scene' represents a sequence of animations and variables+--   that change over time. newtype Scene s a = M { unM :: Time -> ST s (a, Duration, Duration, [Gen s]) }  instance Functor (Scene s) where@@ -183,14 +185,21 @@ instance MonadFix (Scene s) where   mfix fn = M $ \t -> mfix (\v -> let (a, _s, _p, _gens) = v in unM (fn a) t) +-- | Lift an ST action into the Scene monad. liftST :: ST s a -> Scene s a liftST action = M $ \_ -> action >>= \a -> return (a, 0, 0, []) +-- | Evaluate the value of a scene. evalScene :: (forall s . Scene s a) -> a evalScene action = runST $ do   (val, _, _ , _) <- unM action 0   return val +-- | Render a 'Scene' to an 'Animation'.+scene :: (forall s . Scene s a) -> Animation+scene = sceneAnimation++-- | Render a 'Scene' to an 'Animation'. sceneAnimation :: (forall s . Scene s a) -> Animation sceneAnimation action = runST   (do@@ -643,7 +652,22 @@         let (svg', z) = render d t svg in (svg', if t < now - born then z else zindex)     ) --- Destroy all local sprites at the end of a scene.+-- | Destroy all local sprites at the end of a scene.+--+--   Example:+--+--   > do -- the rect lives through the entire 3s animation+--   >    newSpriteSVG_ $ translate (-3) 0 $ mkRect 4 4+--   >    wait 1+--   >    spriteScope $ do+--   >      -- the circle only lives for 1 second.+--   >      local <- newSpriteSVG $ translate 3 0 $ mkCircle 2+--   >      spriteE local $ overBeginning 0.3 fadeInE+--   >      spriteE local $ overEnding 0.3 fadeOutE+--   >      wait 1+--   >    wait 1+--+--   <<docs/gifs/doc_spriteScope.gif>> spriteScope :: Scene s a -> Scene s a spriteScope (M action) = M $ \t -> do   (a, s, p, gens) <- action t@@ -657,10 +681,13 @@         else (None, 0)  asAnimation :: (forall s'. Scene s' a) -> Scene s Animation-asAnimation scene = do+asAnimation s = do   now <- queryNow-  return $ dropA now (sceneAnimation (wait now >> scene))+  return $ dropA now (sceneAnimation (wait now >> s)) +-- | Apply a transformation with a given overlap. This makes sure+--   to keep timestamps intact such that events can still be timed+--   by transcripts. transitionO :: Transition -> Double -> (forall s'. Scene s' a) -> (forall s'. Scene s' b) -> Scene s () transitionO t o a b = do   aA <- asAnimation a@@ -675,16 +702,22 @@ ------------------------------------------------------- -- Objects +-- | Objects can be any Haskell structure as long as it can be rendered to SVG. class Renderable a where   toSVG :: a -> SVG  instance Renderable Tree where   toSVG = id +-- | Objects are SVG nodes (represented as Haskell values) with+--   identity, location, and several other properties that can+--   change over time. data Object s a = Object   { objectSprite :: Sprite s   , objectData   :: Var s (ObjectData a)   }++-- | Container for object properties. data ObjectData a = ObjectData   { _oTranslate   :: (Double, Double)   , _oValueRef    :: a@@ -703,41 +736,60 @@  -- Basic lenses +-- FIXME: Maybe 'position' is a better name.+-- | Object position. Default: \<0,0\> oTranslate :: Lens' (ObjectData a) (Double, Double) oTranslate = lens _oTranslate $ \obj val -> obj { _oTranslate = val } +-- | Rendered SVG node of an object. Does not include context+--   or object properties. Read-only. oSVG :: Getter (ObjectData a) SVG oSVG = to _oSVG +-- | Custom render context. Is applied to the object for every+--   frame that it is shown. oContext :: Lens' (ObjectData a) (SVG -> SVG) oContext = lens _oContext $ \obj val -> obj { _oContext = val  } +-- | Object margins (top, right, bottom, left) in local units. oMargin :: Lens' (ObjectData a) (Double, Double, Double, Double) oMargin = lens _oMargin $ \obj val -> obj { _oMargin = val } +-- | Object bounding-box (minimal X-coordinate, minimal Y-coordinate,+--   width, height). Uses `Reanimate.Svg.BoundingBox.boundingBox`+--   and has the same limitations. oBB :: Getter (ObjectData a) (Double, Double, Double, Double) oBB = to _oBB +-- | Object opacity. Default: 1 oOpacity :: Lens' (ObjectData a) Double oOpacity = lens _oOpacity $ \obj val -> obj { _oOpacity = val } +-- | Toggle for whether or not the object should be rendered.+--   Default: False oShown :: Lens' (ObjectData a) Bool oShown = lens _oShown $ \obj val -> obj { _oShown = val } +-- | Object's z-index. oZIndex :: Lens' (ObjectData a) Int oZIndex = lens _oZIndex $ \obj val -> obj { _oZIndex = val } +-- | Easing function used when modifying object properties.+--   Default: @'Reanimate.Ease.curveS' 2@ oEasing :: Lens' (ObjectData a) Signal oEasing = lens _oEasing $ \obj val -> obj { _oEasing = val } +-- | Object's scale. Default: 1 oScale :: Lens' (ObjectData a) Double oScale = lens _oScale $ \obj val -> obj { _oScale = val } +-- | Origin point for scaling. Default: \<0,0\> oScaleOrigin :: Lens' (ObjectData a) (Double, Double) oScaleOrigin = lens _oScaleOrigin $ \obj val -> obj { _oScaleOrigin = val }  -- Smart lenses +-- | Lens for the source value contained in an object. oValue :: Renderable a => Lens' (ObjectData a) a oValue = lens _oValueRef $ \obj newVal ->     let svg = toSVG newVal@@ -746,6 +798,7 @@     , _oSVG      = svg     , _oBB       = boundingBox svg } +-- | Derived location of the top-most point of an object + margin. oTopY :: Lens' (ObjectData a) Double oTopY = lens getter setter   where@@ -758,6 +811,7 @@     setter obj val =       obj & (oTranslate . _2) +~ val-getter obj +-- | Derived location of the bottom-most point of an object + margin. oBottomY :: Lens' (ObjectData a) Double oBottomY = lens getter setter   where@@ -769,6 +823,7 @@     setter obj val =        obj & (oTranslate . _2) +~ val-getter obj +-- | Derived location of the left-most point of an object + margin. oLeftX :: Lens' (ObjectData a) Double oLeftX = lens getter setter   where@@ -780,6 +835,7 @@     setter obj val =       obj & (oTranslate . _1) +~ val-getter obj +-- | Derived location of the right-most point of an object + margin. oRightX :: Lens' (ObjectData a) Double oRightX = lens getter setter   where@@ -792,6 +848,7 @@     setter obj val =       obj & (oTranslate . _1) +~ val-getter obj +-- | Derived location of an object's center point. oCenterXY :: Lens' (ObjectData a) (Double, Double) oCenterXY = lens getter setter   where@@ -807,59 +864,80 @@       obj & (oTranslate . _1) +~ dx-x           & (oTranslate . _2) +~ dy-y +-- | Object's top margin. oMarginTop :: Lens' (ObjectData a) Double oMarginTop = oMargin . _1 +-- | Object's right margin. oMarginRight :: Lens' (ObjectData a) Double oMarginRight = oMargin . _2 +-- | Object's bottom margin. oMarginBottom :: Lens' (ObjectData a) Double oMarginBottom = oMargin . _3 +-- | Object's left margin. oMarginLeft :: Lens' (ObjectData a) Double oMarginLeft = oMargin . _4 +-- | Object's minimal X-coordinate.. oBBMinX :: Getter (ObjectData a) Double oBBMinX = oBB . _1 +-- | Object's minimal Y-coordinate.. oBBMinY :: Getter (ObjectData a) Double oBBMinY = oBB . _2 +-- | Object's width without margin. oBBWidth :: Getter (ObjectData a) Double oBBWidth = oBB . _3 +-- | Object's height without margin. oBBHeight :: Getter (ObjectData a) Double oBBHeight = oBB . _4 +-------------------------------------------------------------------------------+-- Object modifiers++-- | Modify object properties. oModify :: Object s a -> (ObjectData a -> ObjectData a) -> Scene s () oModify o fn = modifyVar (objectData o) fn +-- | Modify object properties using a stateful API. oModifyS :: Object s a -> (State (ObjectData a) b) -> Scene s () oModifyS o fn = oModify o (execState fn) +-- | Query object property. oRead :: Object s a -> Getting b (ObjectData a) b -> Scene s b-oRead o l = do-  v <- readVar (objectData o)-  return $ view l v+oRead o l = view l <$> readVar (objectData o) +-- | Modify object properties over a set duration. oTween :: Object s a -> Duration -> (Double -> ObjectData a -> ObjectData a) -> Scene s () oTween o d fn = do   -- Read 'easing' var here instead of taking it from 'v'.   -- This allows different easing functions even at the same timestamp.   ease <- oRead o oEasing   tweenVar (objectData o) d (\v t -> fn (ease t) v)-  -- tweenVar ref d (\v t -> fn t v) --- oTweenS :: Object s a -> Duration -> (Double -> ObjectData a -> ObjectData a) -> Scene s ()+-- | Modify object properties over a set duration using a stateful API. oTweenS :: Object s a -> Duration -> (Double -> State (ObjectData a) b) -> Scene s () oTweenS o d fn = oTween o d (\t -> execState (fn t)) +-- | Modify object value over a set duration. This is a convenience function+--   for modifying `oValue`. oTweenV :: Renderable a => Object s a -> Duration -> (Double -> a -> a) -> Scene s () oTweenV o d fn = oTween o d (\t -> oValue %~ fn t) +-- | Modify object value over a set duration using a stateful API. This is a+--   convenience function for modifying `oValue`. oTweenVS :: Renderable a => Object s a -> Duration -> (Double -> State a b) -> Scene s () oTweenVS o d fn = oTween o d (\t -> oValue %~ execState (fn t)) +-- | Create new object.+oNew :: Renderable a => a -> Scene s (Object s a)+oNew = newObject++-- | Create new object. newObject :: Renderable a => a -> Scene s (Object s a) newObject val = do   ref <- newVar ObjectData@@ -888,28 +966,152 @@           withGroupOpacity _oOpacity $           _oContext _oSVG         else None+  spriteModify sprite $ do+    ~ObjectData{_oZIndex=z} <- unVar ref+    pure $ \(img,_) -> (img,z)   return Object     { objectSprite = sprite     , objectData   = ref }   where     svg = toSVG val +-------------------------------------------------------------------------------+-- Graphical transformations++-- | Instantly show object.+oShow :: Object s a -> Scene s ()+oShow o = oModify o $ oShown .~ True++-- | Instantly hide object.+oHide :: Object s a -> Scene s ()+oHide o = oModify o $ oShown .~ False++-- | Fade in object over a set duration.+oFadeIn :: Object s a -> Duration -> Scene s ()+oFadeIn o d = do+  oModify o $ +    oShown   .~ True+  oTweenS o d $ \t ->+    oOpacity *= t++-- | Fade out object over a set duration.+oFadeOut :: Object s a -> Duration -> Scene s ()+oFadeOut o d = do+  oModify o $ +    oShown   .~ True+  oTweenS o d $ \t ->+    oOpacity *= 1-t++-- | Scale in object over a set duration.+oGrow :: Object s a -> Duration -> Scene s ()+oGrow o d = do+  oModify o $ +    oShown .~ True+  oTweenS o d $ \t ->+    oScale *= t++-- | Scale out object over a set duration.+oShrink :: Object s a -> Duration -> Scene s ()+oShrink o d =+  oTweenS o d $ \t ->+    oScale *= 1-t++-- FIXME: Also transform attributes: 'opacity', 'scale', 'scaleOrigin'.+-- | Morph source object into target object over a set duration.+oTransform :: Object s a -> Object s b -> Duration -> Scene s ()+oTransform src dst d = do+    srcSvg <- oRead src oSVG+    srcCtx <- oRead src oContext+    srcEase <- oRead src oEasing+    srcLoc <- oRead src oTranslate+    oModify src $ oShown .~ False+    +    dstSvg <- oRead dst oSVG+    dstCtx <- oRead dst oContext+    dstLoc <- oRead dst oTranslate++    m <- newObject $ Morph 0 (srcCtx srcSvg) (dstCtx dstSvg)+    oModifyS m $ do+      oShown     .= True+      oEasing    .= srcEase+      oTranslate .= srcLoc+    fork $ oTween m d $ \t -> oTranslate %~ moveTo t dstLoc+    oTweenV m d $ \t -> morphDelta .~ t+    oModify m $ oShown .~ False+    oModify dst $ oShown .~ True+  where+    moveTo t (dstX, dstY) (srcX, srcY) =+      (fromToS srcX dstX t, fromToS srcY dstY t)+++-------------------------------------------------------------------------------+-- Built-in objects++-- | Basic object mapping to \<circle\/\> in SVG. newtype Circle = Circle {_circleRadius :: Double}++-- | Circle radius in local units.+circleRadius :: Lens' Circle Double+circleRadius = iso _circleRadius Circle+ instance Renderable Circle where   toSVG (Circle r) = mkCircle r +-- | Basic object mapping to \<rect\/\> in SVG. data Rectangle = Rectangle { _rectWidth :: Double, _rectHeight :: Double }++-- | Rectangle width in local units.+rectWidth :: Lens' Rectangle Double+rectWidth = lens _rectWidth $ \obj val -> obj{_rectWidth=val}++-- | Rectangle height in local units.+rectHeight :: Lens' Rectangle Double+rectHeight = lens _rectHeight $ \obj val -> obj{_rectHeight=val}+ instance Renderable Rectangle where   toSVG (Rectangle w h) = mkRect w h +-- | Object representing an interpolation between SVG nodes. data Morph = Morph { _morphDelta :: Double, _morphSrc :: SVG, _morphDst :: SVG }++-- | Control variable for the interpolation. A value of 0 gives the+--   source SVG and 1 gives the target svg.+morphDelta :: Lens' Morph Double+morphDelta = lens _morphDelta $ \obj val -> obj{_morphDelta = val}++-- | Source shape.+morphSrc :: Lens' Morph SVG+morphSrc = lens _morphSrc $ \obj val -> obj{_morphSrc = val}++-- | Target shape.+morphDst :: Lens' Morph SVG+morphDst = lens _morphDst $ \obj val -> obj{_morphDst = val}+ instance Renderable Morph where   toSVG (Morph t src dst) = morph linear src dst t +-- | Cameras can take control of objects and manipulate them+--   with convenient pan and zoom operations. data Camera = Camera instance Renderable Camera where   toSVG Camera = None +-- | Connect an object to a camera such that+--   camera settings (position, zoom, and rotation) is+--   applied to the object.+--+--   Example+--+--   > do cam <- newObject Camera+--   >    circ <- newObject $ Circle 2+--   >    oModifyS circ $+--   >      oContext .= withFillOpacity 1 . withFillColor "blue"+--   >    oShow circ+--   >    cameraAttach cam circ+--   >    cameraZoom cam 1 2+--   >    cameraZoom cam 1 1+--+--   <<docs/gifs/doc_cameraAttach.gif>> cameraAttach :: Object s Camera -> Object s a -> Scene s () cameraAttach cam obj =   spriteModify (objectSprite obj) $ do@@ -923,6 +1125,25 @@             uncurry translate (camData^.oScaleOrigin & both %~ negate)       in (ctx svg, zindex) +-- |+--+--   Example+--+--   > do cam <- newObject Camera+--   >    circ <- newObject $ Circle 2; oShow circ+--   >    oModify circ $ oTranslate .~ (-3,0)+--   >    box <- newObject $ Rectangle 4 4; oShow box+--   >    oModify box $ oTranslate .~ (3,0)+--   >    cameraAttach cam circ+--   >    cameraAttach cam box+--   >    cameraFocus cam (-3,0)+--   >    cameraZoom cam 2 2      -- Zoom in+--   >    cameraZoom cam 2 1      -- Zoom out+--   >    cameraFocus cam (3,0)+--   >    cameraZoom cam 2 2      -- Zoom in+--   >    cameraZoom cam 2 1      -- Zoom out+--+--   <<docs/gifs/doc_cameraFocus.gif>> cameraFocus :: Object s Camera -> (Double, Double) -> Scene s () cameraFocus cam (x,y) = do   (ox, oy) <- oRead cam oScaleOrigin@@ -933,85 +1154,27 @@     oTranslate .= newLocation     oScaleOrigin .= (x,y) +-- | Instantaneously set camera zoom level. cameraSetZoom :: Object s Camera -> Double -> Scene s () cameraSetZoom cam s =   oModifyS cam $     oScale .= s +-- | Change camera zoom level over a set duration. cameraZoom :: Object s Camera -> Duration -> Double -> Scene s () cameraZoom cam d s =   oTweenS cam d $ \t ->     oScale %= \v -> fromToS v s t +-- | Instantaneously set camera location. cameraSetPan :: Object s Camera -> (Double, Double) -> Scene s () cameraSetPan cam location =   oModifyS cam $ do     oTranslate .= location +-- | Change camera location over a set duration. cameraPan :: Object s Camera -> Duration -> (Double, Double) -> Scene s () cameraPan cam d (x,y) =   oTweenS cam d $ \t -> do     oTranslate._1 %= \v -> fromToS v x t     oTranslate._2 %= \v -> fromToS v y t--makeLenses ''Circle-makeLenses ''Rectangle-makeLenses ''Morph--oShow :: Object s a -> Scene s ()-oShow o = oModify o $ oShown .~ True--oHide :: Object s a -> Scene s ()-oHide o = oModify o $ oShown .~ False--oFadeIn :: Object s a -> Duration -> Scene s ()-oFadeIn o d = do-  oModify o $ -    oShown   .~ True-  oTweenS o d $ \t ->-    oOpacity *= t--oFadeOut :: Object s a -> Duration -> Scene s ()-oFadeOut o d = do-  oModify o $ -    oShown   .~ True-  oTweenS o d $ \t ->-    oOpacity *= 1-t--oGrow :: Object s a -> Duration -> Scene s ()-oGrow o d = do-  oModify o $ -    oShown .~ True-  oTweenS o d $ \t ->-    oScale *= t--oShrink :: Object s a -> Duration -> Scene s ()-oShrink o d =-  oTweenS o d $ \t ->-    oScale *= 1-t---- FIXME: Also transform attributes: 'opacity', 'scale', 'scaleOrigin'.-oTransform :: Object s a -> Object s b -> Duration -> Scene s ()-oTransform src dst d = do-    srcSvg <- oRead src oSVG-    srcCtx <- oRead src oContext-    srcEase <- oRead src oEasing-    srcLoc <- oRead src oTranslate-    oModify src $ oShown .~ False-    -    dstSvg <- oRead dst oSVG-    dstCtx <- oRead dst oContext-    dstLoc <- oRead dst oTranslate--    m <- newObject $ Morph 0 (srcCtx srcSvg) (dstCtx dstSvg)-    oModifyS m $ do-      oShown     .= True-      oEasing    .= srcEase-      oTranslate .= srcLoc-    fork $ oTween m d $ \t -> oTranslate %~ moveTo t dstLoc-    oTweenV m d $ \t -> morphDelta .~ t-    oModify m $ oShown .~ False-    oModify dst $ oShown .~ True-  where-    moveTo t (dstX, dstY) (srcX, srcY) =-      (fromToS srcX dstX t, fromToS srcY dstY t)
src/Reanimate/Svg.hs view
@@ -1,4 +1,11 @@ {-# LANGUAGE LambdaCase #-}+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Svg   ( module Reanimate.Svg   , module Reanimate.Svg.Constructors@@ -20,6 +27,13 @@ import           Reanimate.Svg.Unuse import qualified Reanimate.Transform          as Transform +-- | Remove transformations (such as translations, rotations, scaling)+--   and apply them directly to the SVG nodes. Note, this function+--   may convert nodes (such as Circle or Rect) to paths. Also note+--   that /does/ change how the SVG is rendered. Particularly, stroke+--   width is affected by directly applying scaling.+--+--   @lowerTransformations (scale 2 (mkCircle 1)) = mkCircle 2@ lowerTransformations :: Tree -> Tree lowerTransformations = worker False Transform.identity   where@@ -55,6 +69,7 @@         -- If we haven't tried to pathify, run pathify only once.         _ -> worker True m (pathify t) +-- | Remove all @id@ attributes. lowerIds :: Tree -> Tree lowerIds = mapTree worker   where@@ -62,6 +77,7 @@     worker t@PathTree{}  = t & attrId .~ Nothing     worker t             = t +-- | Optimize SVG tree without affecting how it is rendered. simplify :: Tree -> Tree simplify root =   case worker root of@@ -89,6 +105,11 @@       | null (g^.groupChildren) = []     dropNulls t = [t] +-- | Separate grouped items. This is required by clip nodes.+--+-- @removeGroups (withFillColor "blue" $ mkGroup [mkCircle 1, mkRect 1 1])+--    = [ withFillColor "blue" $ mkCircle 1+--      , withFillColor "blue" $ mkRect 1 1 ]@ removeGroups :: Tree -> [Tree] removeGroups = worker defaultSvg   where@@ -111,6 +132,7 @@       | null (g^.groupChildren) = []     dropNulls t = [t] +-- | Extract all path commands from a node (and its children) and concatenate them. extractPath :: Tree -> [PathCommand] extractPath = worker . simplify . lowerTransformations . pathify   where@@ -118,6 +140,10 @@     worker (PathTree p)  = p^.pathDefinition     worker _             = [] +-- | Map over indexed symbols.+--+--   @withSubglyphs [0,2] (scale 2) (mkGroup [mkCircle 1, mkRect 2, mkEllipse 1 2])+--      = mkGroup [scale 2 (mkCircle 1), mkRect 2, scale 2 (mkEllipse 1 2)]@ withSubglyphs :: [Int] -> (Tree -> Tree) -> Tree -> Tree withSubglyphs target fn = \t -> evalState (worker t) 0   where@@ -142,6 +168,10 @@         then return $ fn svg         else return svg +-- | Split symbols.+--+--   @splitGlyphs [0,2] (mkGroup [mkCircle 1, mkRect 2, mkEllipse 1 2])+--      = ([mkRect 2], [mkCircle 1, mkEllipse 1 2])@ splitGlyphs :: [Int] -> Tree -> (Tree, Tree) splitGlyphs target = \t ->     let (_, l, r) = execState (worker id t) (0, [], [])@@ -182,6 +212,7 @@ [ (\svg -> <g transform="translate(10,10)"><g transform="scale(2)">svg</g></g>, <circle/>) , (\svg -> <g transform="translate(10,10)"><g transform="scale(0.5)">svg</g></g>, <rect/>)] -}+-- | Split symbols and include their context and drawing attributes. svgGlyphs :: Tree -> [(Tree -> Tree, DrawAttributes, Tree)] svgGlyphs = worker id defaultSvg   where@@ -287,6 +318,7 @@         Num d -> Just d         _     -> Nothing +-- | Map over all recursively-found path commands. mapSvgPaths :: ([PathCommand] -> [PathCommand]) -> SVG -> SVG mapSvgPaths fn = mapTree worker   where@@ -296,10 +328,12 @@           path & pathDefinition %~ fn         t -> t +-- | Map over all recursively-found line commands. mapSvgLines :: ([LineCommand] -> [LineCommand]) -> SVG -> SVG mapSvgLines fn = mapSvgPaths (lineToPath . fn . toLineCommands)  -- Only maps points in paths+-- | Map over all line command control points. mapSvgPoints :: (RPoint -> RPoint) -> SVG -> SVG mapSvgPoints fn = mapSvgLines (map worker)   where@@ -307,6 +341,7 @@     worker (LineBezier ps) = LineBezier (map fn ps)     worker (LineEnd p) = LineEnd (fn p) +-- | Convert coordinate system from degrees to radians. svgPointsToRadians :: SVG -> SVG svgPointsToRadians = mapSvgPoints worker   where
src/Reanimate/Svg/BoundingBox.hs view
@@ -1,17 +1,28 @@-module Reanimate.Svg.BoundingBox where+{-|+  Bounding-boxes can be immensely useful for aligning objects+  but they are not part of the SVG specification and cannot be+  computed for all SVG nodes. In particular, you'll get bad results+  when asking for the bounding boxes of Text nodes (because fonts+  are difficult), clipped nodes, and filtered nodes.+-}+module Reanimate.Svg.BoundingBox+  ( boundingBox+  , svgHeight+  , svgWidth+  ) where -import           Control.Arrow             ((***))-import           Control.Lens              ((^.))+import           Control.Arrow ((***))+import           Control.Lens ((^.)) import           Data.List-import           Data.Maybe                (mapMaybe)-import           Graphics.SvgTree          hiding (height, line, path, use,-                                            width)-import           Linear.V2                 hiding (angle)+import           Data.Maybe (mapMaybe)+import qualified Data.Vector.Unboxed as V+import qualified Geom2D.CubicBezier.Linear as Bezier+import           Graphics.SvgTree hiding (height, line, path, use, width)+import           Linear.V2 hiding (angle) import           Linear.Vector import           Reanimate.Constants import           Reanimate.Svg.LineCommand-import qualified Reanimate.Transform       as Transform--- import qualified Geom2D.CubicBezier           as Bezier+import qualified Reanimate.Transform as Transform  -- | Return bounding box of SVG tree. --  The four numbers returned are (minimal X-coordinate, minimal Y-coordinate, width, height)@@ -30,16 +41,21 @@     worker (minx, miny, maxx, maxy) (V2 x y) =       (min minx x, min miny y, max maxx x, max maxy y) +-- | Height of SVG node in local units (not pixels). Computed on best-effort basis+--   and will not give accurate results for all SVG nodes. svgHeight :: Tree -> Double svgHeight t = h   where     (_x, _y, _w, h) = boundingBox t +-- | Width of SVG node in local units (not pixels). Computed on best-effort basis+--   and will not give accurate results for all SVG nodes. svgWidth :: Tree -> Double svgWidth t = w   where     (_x, _y, w, _h) = boundingBox t +-- | Sampling of points in a line path. linePoints :: [LineCommand] -> [RPoint] linePoints = worker zero   where@@ -51,7 +67,8 @@         LineBezier [p] ->           p : worker p xs         LineBezier ctrl -> -- approximation-          [ last (partialBezierPoints (from:ctrl) 0 (recip chunks*i)) | i <- [0..chunks]] +++          let bezier = Bezier.AnyBezier (V.fromList (from:ctrl))+          in [ Bezier.evalBezier bezier (recip chunks*i) | i <- [0..chunks]] ++           worker (last ctrl) xs         LineEnd p -> p : worker p xs     chunks = 10@@ -90,7 +107,7 @@     pointToRPoint p =       case mapTuple (toUserUnit defaultDPI) p of         (Num x, Num y) -> V2 x y-        _ -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format."+        _              -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format."      circleBoundingPoints circ =       let (xnum, ynum) = circ ^. circleCenter
src/Reanimate/Svg/Constructors.hs view
@@ -108,6 +108,7 @@     offsetY = -y-h/2     (x,y,w,h) = boundingBox t +-- | Same as 'aroundCenter' but only for the Y-axis. aroundCenterY :: (Tree -> Tree) -> Tree -> Tree aroundCenterY fn t =     translate 0 (-offsetY) $ fn $ translate 0 offsetY t@@ -115,6 +116,7 @@     offsetY = -y-h/2     (_x,y,_w,h) = boundingBox t +-- | Same as 'aroundCenter' but only for the X-axis. aroundCenterX :: (Tree -> Tree) -> Tree -> Tree aroundCenterX fn t =     translate (-offsetX) 0 $ fn $ translate offsetX 0 t@@ -185,6 +187,7 @@   where     (_x, y, _w, h) = boundingBox t +-- | Center the second argument using the bounding-box of the first. centerUsing :: Tree -> Tree -> Tree centerUsing a = translate (-x-w/2) (-y-h/2)   where@@ -203,9 +206,11 @@ withStrokeColor :: String -> Tree -> Tree withStrokeColor color = strokeColor .~ pure (mkColor color) +-- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke> withStrokeColorPixel :: PixelRGBA8 -> Tree -> Tree withStrokeColorPixel color = strokeColor .~ pure (ColorRef color) +-- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray> withStrokeDashArray :: [Double] -> Tree -> Tree withStrokeDashArray arr = strokeDashArray .~ pure (map Num arr) @@ -217,6 +222,7 @@ withFillColor :: String -> Tree -> Tree withFillColor color = fillColor .~ pure (mkColor color) +-- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill> withFillColorPixel :: PixelRGBA8 -> Tree -> Tree withFillColorPixel color = fillColor .~ pure (ColorRef color) @@ -224,6 +230,7 @@ withFillOpacity :: Double -> Tree -> Tree withFillOpacity opacity = fillOpacity ?~ realToFrac opacity +-- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/opacity> withGroupOpacity :: Double -> Tree -> Tree withGroupOpacity opacity = groupOpacity ?~ realToFrac opacity @@ -347,6 +354,7 @@ mkBackground color = withFillOpacity 1 $  withStrokeWidth 0 $   withFillColor color $ mkRect screenWidth screenHeight +-- | Rectangle with a uniform color and the same size as the screen. mkBackgroundPixel :: PixelRGBA8 -> Tree mkBackgroundPixel pixel =     withFillOpacity 1 $ withStrokeWidth 0 $
src/Reanimate/Svg/LineCommand.hs view
@@ -1,18 +1,35 @@-module Reanimate.Svg.LineCommand where+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-}+module Reanimate.Svg.LineCommand+  ( CmdM+  , LineCommand(..)+  , lineLength+  , toLineCommands+  , lineToPath+  , lineToPoints+  , partialSvg+  ) where -import           Control.Lens        ((%~), (&), (.~))+import           Control.Lens              ((%~), (&), (.~)) import           Control.Monad.Fix import           Control.Monad.State import           Data.Functor-import qualified Data.Vector.Unboxed as V-import qualified Geom2D.CubicBezier  as Bezier-import           Graphics.SvgTree    hiding (height, line, path, use, width)+import qualified Data.Vector.Unboxed       as V+import qualified Geom2D.CubicBezier.Linear as Bezier+import           Graphics.SvgTree          hiding (height, line, path, use, width) import           Linear.Metric-import           Linear.V2           hiding (angle)+import           Linear.V2                 hiding (angle) import           Linear.Vector +-- | Line command monad used for keeping track of the current location. type CmdM a = State RPoint a +-- | Simplified version of a PathCommand where all points are absolute. data LineCommand   = LineMove RPoint   -- | LineDraw RPoint@@ -20,6 +37,7 @@   | LineEnd RPoint   deriving (Show) +-- | Convert from line commands to path commands. lineToPath :: [LineCommand] -> [PathCommand] lineToPath = map worker   where@@ -31,6 +49,7 @@     worker LineBezier{}         = error "Reanimate.Svg.lineToPath: invalid bezier curve"     worker LineEnd{}            = EndPath +-- | Using @n@ control points, approximate the path of the curves. lineToPoints :: Int -> [LineCommand] -> [RPoint] lineToPoints nPoints cmds =     map lineEnd lineSegments@@ -58,10 +77,11 @@ adjustLineLength alpha from cmd =   case cmd of     LineBezier points -> LineBezier $ drop 1 $ partialBezierPoints (from:points) 0 alpha-    LineMove p -> LineMove p+    LineMove p        -> LineMove p     -- LineDraw t -> LineDraw (lerp alpha t from)-    LineEnd p -> LineBezier [lerp alpha p from]+    LineEnd p         -> LineBezier [lerp alpha p from] +-- | Estimated length of all segments in a line. lineLength :: LineCommand -> CmdM Double lineLength cmd =   case cmd of@@ -79,15 +99,13 @@  rpointsToBezier :: [RPoint] -> Bezier.CubicBezier Double rpointsToBezier lst =-  case map toBezierPoint lst of-    [a,b] -> Bezier.CubicBezier a a b b-    [a,b,c] -> Bezier.quadToCubic (Bezier.QuadBezier a b c)+  case lst of+    [a,b]     -> Bezier.CubicBezier a a b b+    [a,b,c]   -> Bezier.quadToCubic (Bezier.QuadBezier a b c)     [a,b,c,d] -> Bezier.CubicBezier a b c d-    _ -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst--toBezierPoint :: RPoint -> Bezier.Point Double-toBezierPoint (V2 a b) = Bezier.Point a b+    _         -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst +-- | Convert from path commands to line commands. toLineCommands :: [PathCommand] -> [LineCommand] toLineCommands ps = evalState (worker zero Nothing ps) zero   where@@ -238,15 +256,9 @@  partialBezierPoints :: [RPoint] -> Double -> Double -> [RPoint] partialBezierPoints ps a b =-  let fromP (V2 i j) = (i, j)-      toP (i, j) = V2 i j-      c1 :: Bezier.AnyBezier Double-      c1 = Bezier.unsafeFromVector (V.fromList $ map fromP ps)-      os = V.toList $ Bezier.toVector $ Bezier.bezierSubsegment c1 a b-  in map toP os--interpolatePathCommands :: Double -> [PathCommand] -> [PathCommand]-interpolatePathCommands alpha = lineToPath . partialLine alpha . toLineCommands+  let c1 = Bezier.AnyBezier (V.fromList ps)+      Bezier.AnyBezier os = Bezier.bezierSubsegment c1 a b+  in V.toList os  {- | Create an image showing portion of a path.      Note that this only affects paths (see 'Reanimate.Svg.Constructors.mkPath').
src/Reanimate/Svg/Unuse.hs view
@@ -1,3 +1,10 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Svg.Unuse   ( replaceUses   , unbox@@ -11,6 +18,7 @@ import           Reanimate.Constants import           Reanimate.Svg.Constructors +-- | Replace all @<use>@ nodes with their definition. replaceUses :: Document -> Document replaceUses doc = doc & elements %~ map (mapTree replace)   where@@ -41,7 +49,7 @@         Just tid -> Map.insert tid tree m  -- FIXME: the viewbox is ignored. Can we use the viewbox as a mask?--- Transform out viewbox. defs and CSS rules are discarded.+-- | Transform out viewbox. Definitions and CSS rules are discarded. unbox :: Document -> Tree unbox doc@Document{_viewBox = Just (_minx, _minw, _width, _height)} =   GroupTree $ defaultSvg@@ -50,6 +58,8 @@   GroupTree $ defaultSvg     & groupChildren .~ doc^.elements +-- | Embed 'Document'. This keeps the entire document intact but makes+--   it more difficult to use, say, `Reanimate.Svg.pathify` on it. embedDocument :: Document -> Tree embedDocument doc =   translate (-screenWidth/2) (screenHeight/2) $
src/Reanimate/Transform.hs view
@@ -1,5 +1,8 @@ {-# LANGUAGE BangPatterns   #-}-{-# LANGUAGE PackageImports #-}+{-|+  2D transformation matrices capable of translating, scaling,+  rotating, and skewing.+-} module Reanimate.Transform   ( identity   , transformPoint@@ -9,22 +12,24 @@  -- XXX: Use Linear.Matrix instead of Data.Matrix to drop the 'matrix' dependency. import           Data.List-import           "matrix" Data.Matrix (Matrix)-import qualified "matrix" Data.Matrix as M+import           Data.Matrix (Matrix)+import qualified Data.Matrix as M import           Data.Maybe import           Graphics.SvgTree import           Linear.V2 -type TMatrix = Matrix Coord--identity :: TMatrix+-- | Identity matrix.+--+--   @transformPoints identity x = x@+identity :: Matrix Coord identity = M.identity 3 -fromList :: [Coord] -> TMatrix+fromList :: [Coord] -> Matrix Coord fromList [a,b,c,d,e,f] = M.fromList 3 3 [a,c,e,b,d,f,0,0,1] fromList _             = error "Reanimate.Transform.fromList: bad input" -transformPoint :: TMatrix -> RPoint -> RPoint+-- | Apply a transformation matrix to a 2D point.+transformPoint :: Matrix Coord -> RPoint -> RPoint transformPoint m (V2 x y) = V2 (a*x +c*y + e) (b*x + d*y +f)   where     !a = M.unsafeGet 1 1 m@@ -35,11 +40,13 @@     !f = M.unsafeGet 2 3 m     -- (a:c:e:b:d:f:_) = M.toList m -mkMatrix :: Maybe [Transformation] -> TMatrix+-- | Convert multiple SVG transformations into a single transformation matrix.+mkMatrix :: Maybe [Transformation] -> Matrix Coord mkMatrix Nothing   = identity mkMatrix (Just ts) = foldl' (*) identity (map transformationMatrix ts) -transformationMatrix :: Transformation -> TMatrix+-- | Convert an SVG transformation into a transformation matrix.+transformationMatrix :: Transformation -> Matrix Coord transformationMatrix transformation =   case transformation of     TransformMatrix a b c d e f -> fromList [a,b,c,d,e,f]@@ -55,7 +62,8 @@     rotate a = fromList [cos r,sin r,-sin r,cos r,0,0]       where r = a * pi / 180 -toTransformation :: TMatrix -> Transformation+-- | Convert a transformation matrix back into an SVG transformation.+toTransformation :: Matrix Coord -> Transformation toTransformation m = TransformMatrix a b c d e f   where     [a,c,e,b,d,f,_,_,_] = M.toList m
src/Reanimate/Transition.hs view
@@ -1,3 +1,10 @@+{-|+Copyright   : Written by David Himmelstrup+License     : Unlicense+Maintainer  : lemmih@gmail.com+Stability   : experimental+Portability : POSIX+-} module Reanimate.Transition   ( Transition   , signalT
src/Reanimate/Voice.hs view
@@ -10,6 +10,7 @@ module Reanimate.Voice   ( Transcript(..)   , TWord(..)+  , Phone(..)   , findWord                -- :: Transcript -> [Text] -> Text -> TWord   , findWords               -- :: Transcript -> [Text] -> Text -> [TWord]   , loadTranscript          -- :: FilePath -> Transcript@@ -41,6 +42,8 @@ import           Reanimate.Svg import           Reanimate.Constants +-- | Aligned transcript. Contains the transcript text as well as+--   timing data for each word. data Transcript = Transcript   { transcriptText  :: Text   , transcriptKeys  :: Map Text Int@@ -51,6 +54,8 @@   parseJSON = withObject "transcript" $ \o ->     Transcript <$> o .: "transcript" <*> pure Map.empty <*> o .: "words" +-- | Spoken word. Includes information about when it was spoken,+--   its duration, and its phonemes. data TWord = TWord   { wordAligned     :: Text   , wordCase        :: Text@@ -86,6 +91,7 @@       <*> o       .:  "word" +-- | Phoneme type data Phone = Phone   { phoneDuration :: Double   , phoneType     :: Text@@ -286,6 +292,7 @@           ]   ] +-- | Helper function for rendering a transcript. annotateWithTranscript :: Transcript -> Scene s () annotateWithTranscript t = forM_ (transcriptWords t) $ \tword -> do   let svg = scale 1 $ latex (wordReference tword)
test/Helpers.hs view
@@ -15,7 +15,6 @@  import           Reanimate.Math.Common import           Reanimate.Math.Polygon-import           Reanimate.Math.EarClip import           Reanimate.Math.SSSP  -- [(0.8,1),(0.04,0.1),(0.05,1)]
test/Properties.hs view
@@ -13,8 +13,8 @@  import           Reanimate.Math.Common import           Reanimate.Math.Polygon-import           Reanimate.Math.EarClip import           Reanimate.Math.SSSP+import           Reanimate.Math.Triangulate import           Helpers  import Debug.Trace@@ -47,9 +47,11 @@ prop_pCycle p = forAll (choose (0,1)) $ \t ->   pIsSimple (pCycle p t) -prop_validEarClip p = isValidTriangulation p (earClip $ pRing p)+-- prop_validEarClip p = isValidTriangulation p (earClip $ pRing p) -prop_validEarCut p = isValidTriangulation p (earCut $ pRing p)+-- prop_validEarCut p = isValidTriangulation p (earCut $ pRing p)++prop_validMonotone p = isValidTriangulation p (triangulate $ pRing p)  -- dualToTrangulation . dual = id prop_dualInv p =
test/UnitTests.hs view
@@ -10,16 +10,29 @@ import           Data.List            (sort) import qualified Data.Text            as T import qualified Data.Text.IO         as T-import           Reanimate.Misc       (runCmd, withTempDir, withTempFile) import           System.Directory import           System.Exit import           System.FilePath import           System.IO+import           System.IO.Temp+import           System.IO.Unsafe import           System.Process import           Test.Tasty import           Test.Tasty.Golden import           Test.Tasty.HUnit +data BuildSystem+  = Cabal+  | Stack++buildSystem :: BuildSystem+buildSystem = unsafePerformIO $ do+  newbuild <- doesDirectoryExist "dist-newstyle"+  stack <- doesDirectoryExist ".stack-work"+  if newbuild then pure Cabal+    else if stack then pure Stack+    else error "Unknown build system."+ unitTestFolder :: FilePath -> IO TestTree unitTestFolder path = do   files <- sort <$> getDirectoryContents path@@ -36,31 +49,41 @@     ]  genGolden :: FilePath -> IO LBS.ByteString-genGolden path = withTempDir $ \tmpDir -> withTempFile ".exe" $ \tmpExecutable -> do-  let ghcOpts = ["-rtsopts", "--make", "-O0", "-Werror", "-Wall"] ++-                ["-odir", tmpDir, "-hidir", tmpDir, "-o", tmpExecutable]-      runOpts = ["+RTS", "-M1G"]-  -- XXX: Check for errors.-  runCmd "stack" $ ["ghc","--", path] ++ ghcOpts+genGolden path =+  withTempDir $ \tmpDir ->+  withTempFile tmpDir "reanimate.exe" $ \tmpExecutable hd -> do+    hClose hd+    let ghcOpts = ["-rtsopts", "--make", "-O0", "-Werror", "-Wall"] +++                  ["-odir", tmpDir, "-hidir", tmpDir, "-o", tmpExecutable] +++                  ["-v0"]+        runOpts = ["+RTS", "-M1G"]+    -- XXX: Check for errors.+    case buildSystem of+      Stack -> callProcess "stack" $ ["ghc","--", path] ++ ghcOpts+      Cabal -> callProcess "cabal" $ ["v2-exec", "ghc","--", "-package", "reanimate", path] ++ ghcOpts -  (inh, outh, errh, pid) <- runInteractiveProcess tmpExecutable (["test"] ++ runOpts)-    Nothing Nothing-  -- hSetBinaryMode outh True-  -- hSetNewlineMode outh universalNewlineMode-  hClose inh-  out <- BS.hGetContents outh-  err <- T.hGetContents errh-  code <- waitForProcess pid-  case code of-    ExitSuccess   -> return $ LBS.fromChunks [out]-    ExitFailure{} -> error $ "Failed to run: " ++ T.unpack err+    (inh, outh, errh, pid) <- runInteractiveProcess tmpExecutable (["test"] ++ runOpts)+      Nothing Nothing+    -- hSetBinaryMode outh True+    -- hSetNewlineMode outh universalNewlineMode+    hClose inh+    out <- BS.hGetContents outh+    err <- T.hGetContents errh+    code <- waitForProcess pid+    case code of+      ExitSuccess   -> return $ LBS.fromChunks [out]+      ExitFailure{} -> error $ "Failed to run: " ++ T.unpack err  compileTestFolder :: FilePath -> IO TestTree compileTestFolder path = do   files <- sort <$> getDirectoryContents path   return $ testGroup "compile"     [ testCase file $ do-        (ret, _stdout, err) <- readProcessWithExitCode "stack" (["ghc","--", fullPath] ++ ghcOpts) ""+        (ret, _stdout, err) <-+          case buildSystem of+            Stack -> readProcessWithExitCode "stack" (["ghc","--", fullPath] ++ ghcOpts) ""+            Cabal -> readProcessWithExitCode "cabal"+              (["v2-exec","ghc","--", "-package", "reanimate", fullPath] ++ ghcOpts) ""         _ <- evaluate (length err)         case ret of           ExitFailure{} -> assertFailure $ "Failed to compile:\n" ++ err@@ -81,7 +104,10 @@       files <- sort <$> getDirectoryContents path       return $ testGroup "videos"         [ testCase dir $ do-            (ret, _stdout, err) <- readProcessWithExitCode "stack" (["ghc","--", "-i"++path</>dir, fullPath] ++ ghcOpts) ""+            (ret, _stdout, err) <-+              case buildSystem of+                Stack  -> readProcessWithExitCode "stack" (["ghc","--", "-i"++path</>dir, fullPath] ++ ghcOpts) ""+                Cabal  -> readProcessWithExitCode "cabal" (["v2-exec", "ghc","--", "-package", "reanimate", "-i"++path</>dir, fullPath] ++ ghcOpts) ""             _ <- evaluate (length err)             case ret of               ExitFailure{} -> assertFailure $ "Failed to compile:\n" ++ err@@ -117,3 +143,6 @@ -- assertMaybe :: String -> Maybe a -> IO a -- assertMaybe _ (Just a)  = return a -- assertMaybe msg Nothing = assertFailure msg++withTempDir :: (FilePath -> IO a) -> IO a+withTempDir = withSystemTempDirectory "reanimate"