How to combine shapes via set operations?

206 Views Asked by At

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.

2

There are 2 best solutions below

2
duplode On BEST ANSWER

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))]]
1
amalloy On

I'm not an expert on Diagrams, but it looks like you are combining stroke paths rather than the shapes they represent. Fill Rules has some interesting things to say about how the Winding fill rule behaves for stroke paths that overlap themselves, which seems relevant to explaining why you get the result you do.

Instead, I'd suggest using the techniques in Composing diagrams, such as atop, to compose the completed shapes.