haskellhaskell-diagrams

How to combine shapes via set operations?


I would like to subtract one shape from another, and then combine the resulting shape with another shape. In my example a square is to be clipped in half and that clipped version is to be extended by a half circle to the right. So I subtract one square from the other via difference and make a union with the whole circle assuming that overlapping areas will just merge. I'm thinking in terms of set operations where ({1,2,3,4} / {3,4}) U {2,3} equals {1,2,3} but in my implementation it equals {1,3}:

import Diagrams.Backend.SVG.CmdLine

{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE TypeFamilies              #-}

import Diagrams.Prelude
import qualified Diagrams.TwoD.Path.Boolean as B

main = mainWith (combination # fc red # bgFrame 0.1 white)
  where
    combination :: QDiagram B V2 Double Any
    combination = strokePath plusCircle
    shorterSquare = B.difference Winding (square 2) (square 2 # translateX 1)

    plusCircle = B.union Winding (circle 1 <> shorterSquare)

But I get this: enter image description here This is not what I want, I want the half circle merged with the rectangle, and the result to be filled just red with no lines inside.


Solution

  • This particular usage of B.difference reverses the direction of the shorterSquare path, so you need to re-reverse it:

    shorterSquare = B.difference Winding (square 2) (square 2 # translateX 1)
        # reversePath
    

    As this is quite subtle, it is worth it to spend a moment describing how I diagnosed it. Firstly, such fill rule wackiness felt quite like the sort of issue caused by path (or face, etc.) orientations. Secondly, redefining shorterSquare as...

    shorterSquare = square 2 # scaleX 0.5 # translateX 0.5
    

    ... gives the expected result. That means the issue has to do with B.difference and the definition of shorterSquare, rather than with B.union. Confirmation can be obtained through pathVertices:

    GHCi> -- Counterclockwise.
    GHCi> pathVertices $ square 2 # scaleX 0.5 # translateX 0.5
    [[P (V2 1.0 (-1.0)),P (V2 0.9999999999999999 1.0),P (V2 (-1.1102230246251565e-16) 1.0),P (V2 (-2.220446049250313e-16) (-1.0))]]
    GHCi> -- Clockwise.
    GHCi> pathVertices $ B.difference Winding (square 2) (square 2 # translateX 1)
    [[P (V2 (-1.0) 1.0),P (V2 0.0 1.0),P (V2 0.0 (-1.0)),P (V2 (-1.0) (-1.0))]]