2

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.

duplode
  • 33,731
  • 7
  • 79
  • 150
peer
  • 4,171
  • 8
  • 42
  • 73

2 Answers2

4

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))]]
duplode
  • 33,731
  • 7
  • 79
  • 150
  • 2
    Yeah, I would call this a bug in `B.difference`. It's not the first time we've run into such issues with boolean path operations. https://github.com/kuribas/cubicbezier/issues/6 is relevant. – Brent Yorgey May 23 '19 at 21:24
  • @BrentYorgey It's good to know it is a known issue. Thanks for chiming in. – duplode May 23 '19 at 23:51
1

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.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • I believe the OP's code is fine, except for the subtle detail I discuss in my answer (so subtle, in fact, that I wonder whether `B.difference` was actually meant to work like that). – duplode May 23 '19 at 01:33